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
|
// Get list of seen announcement IDs
|
||||||
const seenAnnouncementIds = JSON.parse(
|
const seenAnnouncementIds = await window.electron.getSetting('seenAnnouncementIds');
|
||||||
localStorage.getItem('seenAnnouncementIds') || '[]'
|
|
||||||
) as string[];
|
|
||||||
|
|
||||||
// Find ALL unseen announcements (in order)
|
// Find ALL unseen announcements (in order)
|
||||||
const unseenAnnouncementsList = applicableAnnouncements.filter(
|
const unseenAnnouncementsList = applicableAnnouncements.filter(
|
||||||
@@ -101,13 +99,11 @@ export default function AnnouncementModal() {
|
|||||||
loadAnnouncements();
|
loadAnnouncements();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const handleCloseAnnouncement = () => {
|
const handleCloseAnnouncement = async () => {
|
||||||
if (unseenAnnouncements.length === 0) return;
|
if (unseenAnnouncements.length === 0) return;
|
||||||
|
|
||||||
// Get existing seen announcement IDs
|
// Get existing seen announcement IDs
|
||||||
const seenAnnouncementIds = JSON.parse(
|
const seenAnnouncementIds = await window.electron.getSetting('seenAnnouncementIds');
|
||||||
localStorage.getItem('seenAnnouncementIds') || '[]'
|
|
||||||
) as string[];
|
|
||||||
|
|
||||||
// Add all unseen announcement IDs to the seen list
|
// Add all unseen announcement IDs to the seen list
|
||||||
const newSeenIds = [...seenAnnouncementIds];
|
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);
|
setShowAnnouncementModal(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -428,20 +428,20 @@ function ToolCallView({
|
|||||||
notifications,
|
notifications,
|
||||||
isStreamingMessage = false,
|
isStreamingMessage = false,
|
||||||
}: ToolCallViewProps) {
|
}: ToolCallViewProps) {
|
||||||
const [responseStyle, setResponseStyle] = useState(() => localStorage.getItem('response_style'));
|
const [responseStyle, setResponseStyle] = useState<string>('concise');
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const handleStorageChange = () => {
|
// Load initial value from settings
|
||||||
setResponseStyle(localStorage.getItem('response_style'));
|
window.electron.getSetting('responseStyle').then(setResponseStyle);
|
||||||
|
|
||||||
|
const handleStyleChange = () => {
|
||||||
|
window.electron.getSetting('responseStyle').then(setResponseStyle);
|
||||||
};
|
};
|
||||||
|
|
||||||
window.addEventListener('storage', handleStorageChange);
|
window.addEventListener(AppEvents.RESPONSE_STYLE_CHANGED, handleStyleChange);
|
||||||
|
|
||||||
window.addEventListener(AppEvents.RESPONSE_STYLE_CHANGED, handleStorageChange);
|
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
window.removeEventListener('storage', handleStorageChange);
|
window.removeEventListener(AppEvents.RESPONSE_STYLE_CHANGED, handleStyleChange);
|
||||||
window.removeEventListener(AppEvents.RESPONSE_STYLE_CHANGED, handleStorageChange);
|
|
||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
|||||||
@@ -26,14 +26,19 @@ export function CostTracker({ inputTokens = 0, outputTokens = 0, sessionCosts }:
|
|||||||
|
|
||||||
// Check if pricing is enabled
|
// Check if pricing is enabled
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const checkPricingSetting = () => {
|
const loadPricingSetting = async () => {
|
||||||
const stored = localStorage.getItem('show_pricing');
|
const enabled = await window.electron.getSetting('showPricing');
|
||||||
setShowPricing(stored !== 'false');
|
setShowPricing(enabled);
|
||||||
};
|
};
|
||||||
|
|
||||||
checkPricingSetting();
|
loadPricingSetting();
|
||||||
window.addEventListener('storage', checkPricingSetting);
|
|
||||||
return () => window.removeEventListener('storage', checkPricingSetting);
|
const handlePricingChange = () => {
|
||||||
|
loadPricingSetting();
|
||||||
|
};
|
||||||
|
|
||||||
|
window.addEventListener('showPricingChanged', handlePricingChange);
|
||||||
|
return () => window.removeEventListener('showPricingChanged', handlePricingChange);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|||||||
@@ -151,29 +151,18 @@ const SessionHistoryView: React.FC<SessionHistoryViewProps> = ({
|
|||||||
const setView = useNavigation();
|
const setView = useNavigation();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const savedSessionConfig = localStorage.getItem('session_sharing_config');
|
window.electron.getSetting('sessionSharing').then((config) => {
|
||||||
if (savedSessionConfig) {
|
if (config.enabled && config.baseUrl) {
|
||||||
try {
|
setCanShare(true);
|
||||||
const config = JSON.parse(savedSessionConfig);
|
|
||||||
if (config.enabled && config.baseUrl) {
|
|
||||||
setCanShare(true);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error parsing session sharing config:', error);
|
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const handleShare = async () => {
|
const handleShare = async () => {
|
||||||
setIsSharing(true);
|
setIsSharing(true);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const savedSessionConfig = localStorage.getItem('session_sharing_config');
|
const config = await window.electron.getSetting('sessionSharing');
|
||||||
if (!savedSessionConfig) {
|
|
||||||
throw new Error('Session sharing is not configured. Please configure it in settings.');
|
|
||||||
}
|
|
||||||
|
|
||||||
const config = JSON.parse(savedSessionConfig);
|
|
||||||
if (!config.enabled || !config.baseUrl) {
|
if (!config.enabled || !config.baseUrl) {
|
||||||
throw new Error('Session sharing is not enabled or base URL is not configured.');
|
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
|
// Load show pricing setting
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const stored = localStorage.getItem('show_pricing');
|
window.electron.getSetting('showPricing').then(setShowPricing);
|
||||||
setShowPricing(stored !== 'false');
|
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
// Handle scrolling to update section
|
// 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);
|
setShowPricing(checked);
|
||||||
localStorage.setItem('show_pricing', String(checked));
|
await window.electron.setSetting('showPricing', checked);
|
||||||
trackSettingToggled('cost_tracking', checked);
|
trackSettingToggled('cost_tracking', checked);
|
||||||
// Trigger storage event for other components
|
// Trigger event for other components
|
||||||
window.dispatchEvent(new CustomEvent('storage'));
|
window.dispatchEvent(new CustomEvent('showPricingChanged'));
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -3,33 +3,18 @@ import { Switch } from '../../ui/switch';
|
|||||||
import { Input } from '../../ui/input';
|
import { Input } from '../../ui/input';
|
||||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '../../ui/card';
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '../../ui/card';
|
||||||
import { AlertCircle } from 'lucide-react';
|
import { AlertCircle } from 'lucide-react';
|
||||||
import { ExternalGoosedConfig } from '../../../utils/settings';
|
import { ExternalGoosedConfig, defaultSettings } from '../../../utils/settings';
|
||||||
import { WEB_PROTOCOLS } from '../../../utils/urlSecurity';
|
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() {
|
export default function ExternalBackendSection() {
|
||||||
const [config, setConfig] = useState<ExternalGoosedConfig>(DEFAULT_CONFIG);
|
const [config, setConfig] = useState<ExternalGoosedConfig>(defaultSettings.externalGoosed);
|
||||||
const [isSaving, setIsSaving] = useState(false);
|
const [isSaving, setIsSaving] = useState(false);
|
||||||
const [urlError, setUrlError] = useState<string | null>(null);
|
const [urlError, setUrlError] = useState<string | null>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const loadSettings = async () => {
|
const loadSettings = async () => {
|
||||||
const settings = await window.electron.getSettings();
|
const externalGoosed = await window.electron.getSetting('externalGoosed');
|
||||||
setConfig(parseConfig(settings.externalGoosed));
|
setConfig(externalGoosed);
|
||||||
};
|
};
|
||||||
loadSettings();
|
loadSettings();
|
||||||
}, []);
|
}, []);
|
||||||
@@ -56,11 +41,7 @@ export default function ExternalBackendSection() {
|
|||||||
const saveConfig = async (newConfig: ExternalGoosedConfig): Promise<void> => {
|
const saveConfig = async (newConfig: ExternalGoosedConfig): Promise<void> => {
|
||||||
setIsSaving(true);
|
setIsSaving(true);
|
||||||
try {
|
try {
|
||||||
const currentSettings = await window.electron.getSettings();
|
await window.electron.setSetting('externalGoosed', newConfig);
|
||||||
await window.electron.saveSettings({
|
|
||||||
...currentSettings,
|
|
||||||
externalGoosed: newConfig,
|
|
||||||
});
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to save external backend settings:', error);
|
console.error('Failed to save external backend settings:', error);
|
||||||
} finally {
|
} finally {
|
||||||
|
|||||||
@@ -122,8 +122,8 @@ export default function KeyboardShortcutsSection() {
|
|||||||
const [showRestartNotice, setShowRestartNotice] = useState(false);
|
const [showRestartNotice, setShowRestartNotice] = useState(false);
|
||||||
|
|
||||||
const loadShortcuts = useCallback(async () => {
|
const loadShortcuts = useCallback(async () => {
|
||||||
const settings = await window.electron.getSettings();
|
const keyboardShortcuts = await window.electron.getSetting('keyboardShortcuts');
|
||||||
setShortcuts(settings.keyboardShortcuts || defaultKeyboardShortcuts);
|
setShortcuts(keyboardShortcuts || defaultKeyboardShortcuts);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -163,15 +163,11 @@ export default function KeyboardShortcutsSection() {
|
|||||||
newShortcuts[key] = null;
|
newShortcuts[key] = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const settings = await window.electron.getSettings();
|
await window.electron.setSetting('keyboardShortcuts', newShortcuts);
|
||||||
settings.keyboardShortcuts = newShortcuts;
|
setShortcuts(newShortcuts);
|
||||||
const success = await window.electron.saveSettings(settings);
|
trackSettingToggled(`shortcut_${key}`, enabled);
|
||||||
if (success) {
|
if (needsRestart.has(key)) {
|
||||||
setShortcuts(newShortcuts);
|
setShowRestartNotice(true);
|
||||||
trackSettingToggled(`shortcut_${key}`, enabled);
|
|
||||||
if (needsRestart.has(key)) {
|
|
||||||
setShowRestartNotice(true);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -209,15 +205,11 @@ export default function KeyboardShortcutsSection() {
|
|||||||
|
|
||||||
newShortcuts[editingKey] = shortcut || null;
|
newShortcuts[editingKey] = shortcut || null;
|
||||||
|
|
||||||
const settings = await window.electron.getSettings();
|
await window.electron.setSetting('keyboardShortcuts', newShortcuts);
|
||||||
settings.keyboardShortcuts = newShortcuts;
|
setShortcuts(newShortcuts);
|
||||||
const success = await window.electron.saveSettings(settings);
|
setEditingKey(null);
|
||||||
if (success) {
|
if (needsRestart.has(editingKey)) {
|
||||||
setShortcuts(newShortcuts);
|
setShowRestartNotice(true);
|
||||||
setEditingKey(null);
|
|
||||||
if (needsRestart.has(editingKey)) {
|
|
||||||
setShowRestartNotice(true);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -236,14 +228,10 @@ export default function KeyboardShortcutsSection() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (confirmed.response === 0) {
|
if (confirmed.response === 0) {
|
||||||
const settings = await window.electron.getSettings();
|
await window.electron.setSetting('keyboardShortcuts', { ...defaultKeyboardShortcuts });
|
||||||
settings.keyboardShortcuts = { ...defaultKeyboardShortcuts };
|
setShortcuts({ ...defaultKeyboardShortcuts });
|
||||||
const success = await window.electron.saveSettings(settings);
|
setShowRestartNotice(true);
|
||||||
if (success) {
|
trackSettingToggled('shortcuts_reset', true);
|
||||||
setShortcuts({ ...defaultKeyboardShortcuts });
|
|
||||||
setShowRestartNotice(true);
|
|
||||||
trackSettingToggled('shortcuts_reset', true);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -6,23 +6,24 @@ export const ResponseStylesSection = () => {
|
|||||||
const [currentStyle, setCurrentStyle] = useState('concise');
|
const [currentStyle, setCurrentStyle] = useState('concise');
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const savedStyle = localStorage.getItem('response_style');
|
async function loadResponseStyle() {
|
||||||
if (savedStyle) {
|
|
||||||
try {
|
try {
|
||||||
|
const savedStyle = await window.electron.getSetting('responseStyle');
|
||||||
setCurrentStyle(savedStyle);
|
setCurrentStyle(savedStyle);
|
||||||
} catch (error) {
|
} 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) => {
|
const handleStyleChange = async (newStyle: string) => {
|
||||||
setCurrentStyle(newStyle);
|
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
|
// Dispatch custom event to notify other components of the change
|
||||||
window.dispatchEvent(new CustomEvent(AppEvents.RESPONSE_STYLE_CHANGED));
|
window.dispatchEvent(new CustomEvent(AppEvents.RESPONSE_STYLE_CHANGED));
|
||||||
|
|||||||
@@ -27,25 +27,19 @@ export default function SessionSharingSection() {
|
|||||||
sessionSharingConfig.enabled &&
|
sessionSharingConfig.enabled &&
|
||||||
isValidUrl(String(sessionSharingConfig.baseUrl));
|
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(() => {
|
useEffect(() => {
|
||||||
if (envBaseUrlShare) {
|
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 = {
|
const forcedConfig = {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
baseUrl: typeof envBaseUrlShare === 'string' ? envBaseUrlShare : '',
|
baseUrl: typeof envBaseUrlShare === 'string' ? envBaseUrlShare : '',
|
||||||
};
|
};
|
||||||
localStorage.setItem('session_sharing_config', JSON.stringify(forcedConfig));
|
window.electron.setSetting('sessionSharing', forcedConfig);
|
||||||
} else {
|
} else {
|
||||||
const savedSessionConfig = localStorage.getItem('session_sharing_config');
|
window.electron.getSetting('sessionSharing').then((config) => {
|
||||||
if (savedSessionConfig) {
|
setSessionSharingConfig(config);
|
||||||
try {
|
});
|
||||||
const config = JSON.parse(savedSessionConfig);
|
|
||||||
setSessionSharingConfig(config);
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error parsing session sharing config:', error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}, [envBaseUrlShare]);
|
}, [envBaseUrlShare]);
|
||||||
|
|
||||||
@@ -61,20 +55,18 @@ export default function SessionSharingSection() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Toggle sharing (only allowed when env is not set).
|
// Toggle sharing (only allowed when env is not set).
|
||||||
const toggleSharing = () => {
|
const toggleSharing = async () => {
|
||||||
if (envBaseUrlShare) {
|
if (envBaseUrlShare) {
|
||||||
return; // Do nothing if the environment variable forces sharing.
|
return; // Do nothing if the environment variable forces sharing.
|
||||||
}
|
}
|
||||||
setSessionSharingConfig((prev) => {
|
const updated = { ...sessionSharingConfig, enabled: !sessionSharingConfig.enabled };
|
||||||
const updated = { ...prev, enabled: !prev.enabled };
|
setSessionSharingConfig(updated);
|
||||||
localStorage.setItem('session_sharing_config', JSON.stringify(updated));
|
await window.electron.setSetting('sessionSharing', updated);
|
||||||
trackSettingToggled('session_sharing', updated.enabled);
|
trackSettingToggled('session_sharing', updated.enabled);
|
||||||
return updated;
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Handle changes to the base URL field
|
// 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;
|
const newBaseUrl = e.target.value;
|
||||||
setSessionSharingConfig((prev) => ({
|
setSessionSharingConfig((prev) => ({
|
||||||
...prev,
|
...prev,
|
||||||
@@ -87,7 +79,7 @@ export default function SessionSharingSection() {
|
|||||||
if (isValidUrl(newBaseUrl)) {
|
if (isValidUrl(newBaseUrl)) {
|
||||||
setUrlError('');
|
setUrlError('');
|
||||||
const updated = { ...sessionSharingConfig, baseUrl: newBaseUrl };
|
const updated = { ...sessionSharingConfig, baseUrl: newBaseUrl };
|
||||||
localStorage.setItem('session_sharing_config', JSON.stringify(updated));
|
await window.electron.setSetting('sessionSharing', updated);
|
||||||
} else {
|
} else {
|
||||||
setUrlError('Invalid URL format. Please enter a valid URL (e.g. https://example.com/api).');
|
setUrlError('Invalid URL format. Please enter a valid URL (e.g. https://example.com/api).');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,29 +22,6 @@ function resolveTheme(preference: ThemePreference): ResolvedTheme {
|
|||||||
return preference;
|
return preference;
|
||||||
}
|
}
|
||||||
|
|
||||||
function loadThemePreference(): ThemePreference {
|
|
||||||
const useSystemTheme = localStorage.getItem('use_system_theme');
|
|
||||||
if (useSystemTheme === 'true') {
|
|
||||||
return 'system';
|
|
||||||
}
|
|
||||||
|
|
||||||
const savedTheme = localStorage.getItem('theme');
|
|
||||||
if (savedTheme === 'dark') {
|
|
||||||
return 'dark';
|
|
||||||
}
|
|
||||||
|
|
||||||
return 'light';
|
|
||||||
}
|
|
||||||
|
|
||||||
function saveThemePreference(preference: ThemePreference): void {
|
|
||||||
if (preference === 'system') {
|
|
||||||
localStorage.setItem('use_system_theme', 'true');
|
|
||||||
} else {
|
|
||||||
localStorage.setItem('use_system_theme', 'false');
|
|
||||||
localStorage.setItem('theme', preference);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function applyThemeToDocument(theme: ResolvedTheme): void {
|
function applyThemeToDocument(theme: ResolvedTheme): void {
|
||||||
const toRemove = theme === 'dark' ? 'light' : 'dark';
|
const toRemove = theme === 'dark' ? 'light' : 'dark';
|
||||||
document.documentElement.classList.add(theme);
|
document.documentElement.classList.add(theme);
|
||||||
@@ -56,19 +33,53 @@ interface ThemeProviderProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function ThemeProvider({ children }: ThemeProviderProps) {
|
export function ThemeProvider({ children }: ThemeProviderProps) {
|
||||||
const [userThemePreference, setUserThemePreferenceState] =
|
// Start with light theme to avoid flash, will update once settings load
|
||||||
useState<ThemePreference>(loadThemePreference);
|
const [userThemePreference, setUserThemePreferenceState] = useState<ThemePreference>('light');
|
||||||
const [resolvedTheme, setResolvedTheme] = useState<ResolvedTheme>(() =>
|
const [resolvedTheme, setResolvedTheme] = useState<ResolvedTheme>('light');
|
||||||
resolveTheme(loadThemePreference())
|
|
||||||
);
|
|
||||||
|
|
||||||
const setUserThemePreference = useCallback((preference: ThemePreference) => {
|
useEffect(() => {
|
||||||
|
async function loadThemeFromSettings() {
|
||||||
|
try {
|
||||||
|
const [useSystemTheme, savedTheme] = await Promise.all([
|
||||||
|
window.electron.getSetting('useSystemTheme'),
|
||||||
|
window.electron.getSetting('theme'),
|
||||||
|
]);
|
||||||
|
|
||||||
|
let preference: ThemePreference;
|
||||||
|
if (useSystemTheme) {
|
||||||
|
preference = 'system';
|
||||||
|
} else {
|
||||||
|
preference = savedTheme;
|
||||||
|
}
|
||||||
|
|
||||||
|
setUserThemePreferenceState(preference);
|
||||||
|
setResolvedTheme(resolveTheme(preference));
|
||||||
|
} catch (error) {
|
||||||
|
console.warn('[ThemeContext] Failed to load theme settings:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
loadThemeFromSettings();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const setUserThemePreference = useCallback(async (preference: ThemePreference) => {
|
||||||
setUserThemePreferenceState(preference);
|
setUserThemePreferenceState(preference);
|
||||||
saveThemePreference(preference);
|
|
||||||
|
|
||||||
const resolved = resolveTheme(preference);
|
const resolved = resolveTheme(preference);
|
||||||
setResolvedTheme(resolved);
|
setResolvedTheme(resolved);
|
||||||
|
|
||||||
|
// Save to settings
|
||||||
|
try {
|
||||||
|
if (preference === 'system') {
|
||||||
|
await window.electron.setSetting('useSystemTheme', true);
|
||||||
|
} else {
|
||||||
|
await window.electron.setSetting('useSystemTheme', false);
|
||||||
|
await window.electron.setSetting('theme', preference);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.warn('[ThemeContext] Failed to save theme settings:', error);
|
||||||
|
}
|
||||||
|
|
||||||
// Broadcast to other windows via Electron
|
// Broadcast to other windows via Electron
|
||||||
window.electron?.broadcastThemeChange({
|
window.electron?.broadcastThemeChange({
|
||||||
mode: resolved,
|
mode: resolved,
|
||||||
@@ -104,8 +115,15 @@ export function ThemeProvider({ children }: ThemeProviderProps) {
|
|||||||
: 'light';
|
: 'light';
|
||||||
|
|
||||||
setUserThemePreferenceState(newPreference);
|
setUserThemePreferenceState(newPreference);
|
||||||
saveThemePreference(newPreference);
|
|
||||||
setResolvedTheme(resolveTheme(newPreference));
|
setResolvedTheme(resolveTheme(newPreference));
|
||||||
|
|
||||||
|
// Save to settings (don't await, fire and forget)
|
||||||
|
if (newPreference === 'system') {
|
||||||
|
window.electron.setSetting('useSystemTheme', true);
|
||||||
|
} else {
|
||||||
|
window.electron.setSetting('useSystemTheme', false);
|
||||||
|
window.electron.setSetting('theme', newPreference);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
window.electron.on('theme-changed', handleThemeChanged);
|
window.electron.on('theme-changed', handleThemeChanged);
|
||||||
|
|||||||
+53
-25
@@ -30,8 +30,8 @@ import log from './utils/logger';
|
|||||||
import { ensureWinShims } from './utils/winShims';
|
import { ensureWinShims } from './utils/winShims';
|
||||||
import { addRecentDir, loadRecentDirs } from './utils/recentDirs';
|
import { addRecentDir, loadRecentDirs } from './utils/recentDirs';
|
||||||
import { formatAppName, errorMessage, formatErrorForLogging } from './utils/conversionUtils';
|
import { formatAppName, errorMessage, formatErrorForLogging } from './utils/conversionUtils';
|
||||||
import type { Settings } from './utils/settings';
|
import type { Settings, SettingKey } from './utils/settings';
|
||||||
import { defaultKeyboardShortcuts, getKeyboardShortcuts } from './utils/settings';
|
import { defaultSettings, getKeyboardShortcuts } from './utils/settings';
|
||||||
import * as crypto from 'crypto';
|
import * as crypto from 'crypto';
|
||||||
import * as yaml from 'yaml';
|
import * as yaml from 'yaml';
|
||||||
import windowStateKeeper from 'electron-window-state';
|
import windowStateKeeper from 'electron-window-state';
|
||||||
@@ -57,18 +57,27 @@ function shouldSetupUpdater(): boolean {
|
|||||||
// Settings management
|
// Settings management
|
||||||
const SETTINGS_FILE = path.join(app.getPath('userData'), 'settings.json');
|
const SETTINGS_FILE = path.join(app.getPath('userData'), 'settings.json');
|
||||||
|
|
||||||
const defaultSettings: Settings = {
|
|
||||||
showMenuBarIcon: true,
|
|
||||||
showDockIcon: true,
|
|
||||||
enableWakelock: false,
|
|
||||||
spellcheckEnabled: true,
|
|
||||||
keyboardShortcuts: defaultKeyboardShortcuts,
|
|
||||||
};
|
|
||||||
|
|
||||||
function getSettings(): Settings {
|
function getSettings(): Settings {
|
||||||
if (fsSync.existsSync(SETTINGS_FILE)) {
|
if (fsSync.existsSync(SETTINGS_FILE)) {
|
||||||
const data = fsSync.readFileSync(SETTINGS_FILE, 'utf8');
|
const data = fsSync.readFileSync(SETTINGS_FILE, 'utf8');
|
||||||
return JSON.parse(data);
|
const stored = JSON.parse(data) as Partial<Settings>;
|
||||||
|
// Deep merge to ensure nested objects get their defaults too
|
||||||
|
return {
|
||||||
|
...defaultSettings,
|
||||||
|
...stored,
|
||||||
|
externalGoosed: {
|
||||||
|
...defaultSettings.externalGoosed,
|
||||||
|
...(stored.externalGoosed ?? {}),
|
||||||
|
},
|
||||||
|
keyboardShortcuts: {
|
||||||
|
...defaultSettings.keyboardShortcuts,
|
||||||
|
...(stored.keyboardShortcuts ?? {}),
|
||||||
|
},
|
||||||
|
sessionSharing: {
|
||||||
|
...defaultSettings.sessionSharing,
|
||||||
|
...(stored.sessionSharing ?? {}),
|
||||||
|
},
|
||||||
|
};
|
||||||
}
|
}
|
||||||
return defaultSettings;
|
return defaultSettings;
|
||||||
}
|
}
|
||||||
@@ -1192,25 +1201,44 @@ ipcMain.handle('add-recent-dir', (_event, dir: string) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Handle scheduling engine settings
|
ipcMain.handle('get-setting', (_event, key: SettingKey) => {
|
||||||
ipcMain.handle('get-settings', () => {
|
const settings = getSettings();
|
||||||
return getSettings(); // Always returns Settings (uses defaults as fallback)
|
return settings[key];
|
||||||
});
|
});
|
||||||
|
|
||||||
ipcMain.handle('save-settings', (_event, settings) => {
|
// Valid setting keys for runtime validation
|
||||||
const oldSettings = getSettings();
|
const validSettingKeys: Set<string> = new Set([
|
||||||
|
'showMenuBarIcon',
|
||||||
|
'showDockIcon',
|
||||||
|
'enableWakelock',
|
||||||
|
'spellcheckEnabled',
|
||||||
|
'externalGoosed',
|
||||||
|
'globalShortcut',
|
||||||
|
'keyboardShortcuts',
|
||||||
|
'theme',
|
||||||
|
'useSystemTheme',
|
||||||
|
'responseStyle',
|
||||||
|
'showPricing',
|
||||||
|
'sessionSharing',
|
||||||
|
'seenAnnouncementIds',
|
||||||
|
]);
|
||||||
|
|
||||||
const oldShortcuts = getKeyboardShortcuts(oldSettings);
|
ipcMain.handle('set-setting', (_event, key: SettingKey, value: unknown) => {
|
||||||
const newShortcuts = getKeyboardShortcuts(settings);
|
// Validate key at runtime to prevent prototype pollution
|
||||||
const shortcutsChanged = JSON.stringify(oldShortcuts) !== JSON.stringify(newShortcuts);
|
if (!validSettingKeys.has(key)) {
|
||||||
|
console.error(`Invalid setting key rejected: ${key}`);
|
||||||
fsSync.writeFileSync(SETTINGS_FILE, JSON.stringify(settings, null, 2));
|
return;
|
||||||
|
|
||||||
if (shortcutsChanged) {
|
|
||||||
registerGlobalShortcuts();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
const settings = getSettings();
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
(settings as any)[key] = value;
|
||||||
|
fsSync.writeFileSync(SETTINGS_FILE, JSON.stringify(settings, null, 2));
|
||||||
|
|
||||||
|
// Re-register shortcuts if keyboard shortcuts changed
|
||||||
|
if (key === 'keyboardShortcuts') {
|
||||||
|
registerGlobalShortcuts();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
ipcMain.handle('get-secret-key', () => {
|
ipcMain.handle('get-secret-key', () => {
|
||||||
|
|||||||
@@ -1,7 +1,45 @@
|
|||||||
import Electron, { contextBridge, ipcRenderer, webUtils } from 'electron';
|
import Electron, { contextBridge, ipcRenderer, webUtils } from 'electron';
|
||||||
import { Recipe } from './recipe';
|
import { Recipe } from './recipe';
|
||||||
import { GooseApp } from './api';
|
import { GooseApp } from './api';
|
||||||
import type { Settings } from './utils/settings';
|
import type { Settings, SettingKey } from './utils/settings';
|
||||||
|
import { defaultSettings } from './utils/settings';
|
||||||
|
|
||||||
|
// Mapping from settings keys to their old localStorage keys for lazy migration
|
||||||
|
const localStorageKeyMap: Partial<Record<SettingKey, string>> = {
|
||||||
|
theme: 'theme',
|
||||||
|
useSystemTheme: 'use_system_theme',
|
||||||
|
responseStyle: 'response_style',
|
||||||
|
showPricing: 'show_pricing',
|
||||||
|
sessionSharing: 'session_sharing_config',
|
||||||
|
seenAnnouncementIds: 'seenAnnouncementIds',
|
||||||
|
};
|
||||||
|
|
||||||
|
// Parse localStorage value based on the setting key
|
||||||
|
function parseLocalStorageValue<K extends SettingKey>(
|
||||||
|
key: K,
|
||||||
|
rawValue: string
|
||||||
|
): Settings[K] | null {
|
||||||
|
try {
|
||||||
|
switch (key) {
|
||||||
|
case 'theme':
|
||||||
|
return (rawValue === 'dark' || rawValue === 'light' ? rawValue : null) as Settings[K];
|
||||||
|
case 'useSystemTheme':
|
||||||
|
return (rawValue === 'true') as unknown as Settings[K];
|
||||||
|
case 'responseStyle':
|
||||||
|
return rawValue as Settings[K];
|
||||||
|
case 'showPricing':
|
||||||
|
return (rawValue === 'true') as unknown as Settings[K];
|
||||||
|
case 'sessionSharing':
|
||||||
|
return JSON.parse(rawValue) as Settings[K];
|
||||||
|
case 'seenAnnouncementIds':
|
||||||
|
return JSON.parse(rawValue) as Settings[K];
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
interface NotificationData {
|
interface NotificationData {
|
||||||
title: string;
|
title: string;
|
||||||
@@ -86,8 +124,8 @@ type ElectronAPI = {
|
|||||||
getMenuBarIconState: () => Promise<boolean>;
|
getMenuBarIconState: () => Promise<boolean>;
|
||||||
setDockIcon: (show: boolean) => Promise<boolean>;
|
setDockIcon: (show: boolean) => Promise<boolean>;
|
||||||
getDockIconState: () => Promise<boolean>;
|
getDockIconState: () => Promise<boolean>;
|
||||||
getSettings: () => Promise<Settings>;
|
getSetting: <K extends SettingKey>(key: K) => Promise<Settings[K]>;
|
||||||
saveSettings: (settings: Settings) => Promise<boolean>;
|
setSetting: <K extends SettingKey>(key: K, value: Settings[K]) => Promise<void>;
|
||||||
getSecretKey: () => Promise<string>;
|
getSecretKey: () => Promise<string>;
|
||||||
getGoosedHostPort: () => Promise<string | null>;
|
getGoosedHostPort: () => Promise<string | null>;
|
||||||
setWakelock: (enable: boolean) => Promise<boolean>;
|
setWakelock: (enable: boolean) => Promise<boolean>;
|
||||||
@@ -190,8 +228,33 @@ const electronAPI: ElectronAPI = {
|
|||||||
getMenuBarIconState: () => ipcRenderer.invoke('get-menu-bar-icon-state'),
|
getMenuBarIconState: () => ipcRenderer.invoke('get-menu-bar-icon-state'),
|
||||||
setDockIcon: (show: boolean) => ipcRenderer.invoke('set-dock-icon', show),
|
setDockIcon: (show: boolean) => ipcRenderer.invoke('set-dock-icon', show),
|
||||||
getDockIconState: () => ipcRenderer.invoke('get-dock-icon-state'),
|
getDockIconState: () => ipcRenderer.invoke('get-dock-icon-state'),
|
||||||
getSettings: () => ipcRenderer.invoke('get-settings'),
|
getSetting: async <K extends SettingKey>(key: K): Promise<Settings[K]> => {
|
||||||
saveSettings: (settings: unknown) => ipcRenderer.invoke('save-settings', settings),
|
try {
|
||||||
|
// Check for localStorage value first (lazy migration)
|
||||||
|
const localStorageKey = localStorageKeyMap[key];
|
||||||
|
if (localStorageKey) {
|
||||||
|
const rawValue = localStorage.getItem(localStorageKey);
|
||||||
|
if (rawValue !== null) {
|
||||||
|
const parsed = parseLocalStorageValue(key, rawValue);
|
||||||
|
if (parsed !== null) {
|
||||||
|
return parsed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return await ipcRenderer.invoke('get-setting', key);
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Failed to get setting '${key}', using default`, error);
|
||||||
|
return defaultSettings[key];
|
||||||
|
}
|
||||||
|
},
|
||||||
|
setSetting: async <K extends SettingKey>(key: K, value: Settings[K]): Promise<void> => {
|
||||||
|
// Clear any localStorage version when writing
|
||||||
|
const localStorageKey = localStorageKeyMap[key];
|
||||||
|
if (localStorageKey) {
|
||||||
|
localStorage.removeItem(localStorageKey);
|
||||||
|
}
|
||||||
|
return ipcRenderer.invoke('set-setting', key, value);
|
||||||
|
},
|
||||||
getSecretKey: () => ipcRenderer.invoke('get-secret-key'),
|
getSecretKey: () => ipcRenderer.invoke('get-secret-key'),
|
||||||
getGoosedHostPort: () => ipcRenderer.invoke('get-goosed-host-port'),
|
getGoosedHostPort: () => ipcRenderer.invoke('get-goosed-host-port'),
|
||||||
setWakelock: (enable: boolean) => ipcRenderer.invoke('set-wakelock', enable),
|
setWakelock: (enable: boolean) => ipcRenderer.invoke('set-wakelock', enable),
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import SuspenseLoader from './suspense-loader';
|
|||||||
import { client } from './api/client.gen';
|
import { client } from './api/client.gen';
|
||||||
import { setTelemetryEnabled } from './utils/analytics';
|
import { setTelemetryEnabled } from './utils/analytics';
|
||||||
import { readConfig } from './api';
|
import { readConfig } from './api';
|
||||||
|
|
||||||
const App = lazy(() => import('./App'));
|
const App = lazy(() => import('./App'));
|
||||||
|
|
||||||
const TELEMETRY_CONFIG_KEY = 'GOOSE_TELEMETRY_ENABLED';
|
const TELEMETRY_CONFIG_KEY = 'GOOSE_TELEMETRY_ENABLED';
|
||||||
|
|||||||
@@ -26,27 +26,15 @@ export async function openSharedSessionFromDeepLink(
|
|||||||
throw new Error('Invalid URL: Missing share token');
|
throw new Error('Invalid URL: Missing share token');
|
||||||
}
|
}
|
||||||
|
|
||||||
// If no baseUrl is provided, check if there's one in localStorage
|
// If no baseUrl is provided, check if there's one in settings
|
||||||
if (!baseUrl) {
|
if (!baseUrl) {
|
||||||
const savedSessionConfig = localStorage.getItem('session_sharing_config');
|
const config = await window.electron.getSetting('sessionSharing');
|
||||||
if (savedSessionConfig) {
|
if (config.enabled && config.baseUrl) {
|
||||||
try {
|
baseUrl = config.baseUrl;
|
||||||
const config = JSON.parse(savedSessionConfig);
|
|
||||||
if (config.enabled && config.baseUrl) {
|
|
||||||
baseUrl = config.baseUrl;
|
|
||||||
} else {
|
|
||||||
throw new Error(
|
|
||||||
'Session sharing is not enabled or base URL is not configured. Check the settings page.'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error parsing session sharing config:', error);
|
|
||||||
throw new Error(
|
|
||||||
'Session sharing is not enabled or base URL is not configured. Check the settings page.'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
throw new Error('Session sharing is not configured');
|
throw new Error(
|
||||||
|
'Session sharing is not enabled or base URL is not configured. Check the settings page.'
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -42,36 +42,50 @@ Object.assign(navigator, {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Mock settings store for tests
|
||||||
|
const mockSettings: Record<string, unknown> = {
|
||||||
|
showMenuBarIcon: true,
|
||||||
|
showDockIcon: true,
|
||||||
|
enableWakelock: false,
|
||||||
|
spellcheckEnabled: true,
|
||||||
|
keyboardShortcuts: {
|
||||||
|
focusWindow: 'CommandOrControl+Alt+G',
|
||||||
|
quickLauncher: 'CommandOrControl+Alt+Shift+G',
|
||||||
|
newChat: 'CommandOrControl+T',
|
||||||
|
newChatWindow: 'CommandOrControl+N',
|
||||||
|
openDirectory: 'CommandOrControl+O',
|
||||||
|
settings: 'CommandOrControl+,',
|
||||||
|
find: 'CommandOrControl+F',
|
||||||
|
findNext: 'CommandOrControl+G',
|
||||||
|
findPrevious: 'CommandOrControl+Shift+G',
|
||||||
|
alwaysOnTop: 'CommandOrControl+Shift+T',
|
||||||
|
},
|
||||||
|
externalGoosed: {
|
||||||
|
enabled: false,
|
||||||
|
url: '',
|
||||||
|
secret: '',
|
||||||
|
},
|
||||||
|
theme: 'light',
|
||||||
|
useSystemTheme: true,
|
||||||
|
responseStyle: 'concise',
|
||||||
|
showPricing: true,
|
||||||
|
sessionSharing: {
|
||||||
|
enabled: false,
|
||||||
|
baseUrl: '',
|
||||||
|
},
|
||||||
|
seenAnnouncementIds: [],
|
||||||
|
};
|
||||||
|
|
||||||
// Mock window.electron for renderer process
|
// Mock window.electron for renderer process
|
||||||
Object.defineProperty(window, 'electron', {
|
Object.defineProperty(window, 'electron', {
|
||||||
writable: true,
|
writable: true,
|
||||||
value: {
|
value: {
|
||||||
platform: 'darwin',
|
platform: 'darwin',
|
||||||
getSettings: vi.fn(() =>
|
getSetting: vi.fn((key: string) => Promise.resolve(mockSettings[key])),
|
||||||
Promise.resolve({
|
setSetting: vi.fn((key: string, value: unknown) => {
|
||||||
envToggles: {
|
mockSettings[key] = value;
|
||||||
GOOSE_SERVER__MEMORY: false,
|
return Promise.resolve();
|
||||||
GOOSE_SERVER__COMPUTER_CONTROLLER: false,
|
}),
|
||||||
},
|
|
||||||
showMenuBarIcon: true,
|
|
||||||
showDockIcon: true,
|
|
||||||
enableWakelock: false,
|
|
||||||
spellcheckEnabled: true,
|
|
||||||
keyboardShortcuts: {
|
|
||||||
focusWindow: 'CommandOrControl+Alt+G',
|
|
||||||
quickLauncher: 'CommandOrControl+Alt+Shift+G',
|
|
||||||
newChat: 'CommandOrControl+T',
|
|
||||||
newChatWindow: 'CommandOrControl+N',
|
|
||||||
openDirectory: 'CommandOrControl+O',
|
|
||||||
settings: 'CommandOrControl+,',
|
|
||||||
find: 'CommandOrControl+F',
|
|
||||||
findNext: 'CommandOrControl+G',
|
|
||||||
findPrevious: 'CommandOrControl+Shift+G',
|
|
||||||
alwaysOnTop: 'CommandOrControl+Shift+T',
|
|
||||||
},
|
|
||||||
})
|
|
||||||
),
|
|
||||||
saveSettings: vi.fn(() => Promise.resolve(true)),
|
|
||||||
showMessageBox: vi.fn(() => Promise.resolve({ response: 0 })),
|
showMessageBox: vi.fn(() => Promise.resolve({ response: 0 })),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -21,16 +21,32 @@ export type DefaultKeyboardShortcuts = {
|
|||||||
[K in keyof KeyboardShortcuts]: string;
|
[K in keyof KeyboardShortcuts]: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export interface SessionSharingConfig {
|
||||||
|
enabled: boolean;
|
||||||
|
baseUrl: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface Settings {
|
export interface Settings {
|
||||||
|
// Desktop app settings
|
||||||
showMenuBarIcon: boolean;
|
showMenuBarIcon: boolean;
|
||||||
showDockIcon: boolean;
|
showDockIcon: boolean;
|
||||||
enableWakelock: boolean;
|
enableWakelock: boolean;
|
||||||
spellcheckEnabled: boolean;
|
spellcheckEnabled: boolean;
|
||||||
externalGoosed?: ExternalGoosedConfig;
|
externalGoosed: ExternalGoosedConfig;
|
||||||
globalShortcut?: string | null;
|
globalShortcut?: string | null;
|
||||||
keyboardShortcuts?: KeyboardShortcuts;
|
keyboardShortcuts: KeyboardShortcuts;
|
||||||
|
|
||||||
|
// UI preferences (migrated from localStorage)
|
||||||
|
theme: 'dark' | 'light';
|
||||||
|
useSystemTheme: boolean;
|
||||||
|
responseStyle: string;
|
||||||
|
showPricing: boolean;
|
||||||
|
sessionSharing: SessionSharingConfig;
|
||||||
|
seenAnnouncementIds: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type SettingKey = keyof Settings;
|
||||||
|
|
||||||
export const defaultKeyboardShortcuts: DefaultKeyboardShortcuts = {
|
export const defaultKeyboardShortcuts: DefaultKeyboardShortcuts = {
|
||||||
focusWindow: 'CommandOrControl+Alt+G',
|
focusWindow: 'CommandOrControl+Alt+G',
|
||||||
quickLauncher: 'CommandOrControl+Alt+Shift+G',
|
quickLauncher: 'CommandOrControl+Alt+Shift+G',
|
||||||
@@ -44,6 +60,31 @@ export const defaultKeyboardShortcuts: DefaultKeyboardShortcuts = {
|
|||||||
alwaysOnTop: 'CommandOrControl+Shift+T',
|
alwaysOnTop: 'CommandOrControl+Shift+T',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const defaultSettings: Settings = {
|
||||||
|
// Desktop app settings
|
||||||
|
showMenuBarIcon: true,
|
||||||
|
showDockIcon: true,
|
||||||
|
enableWakelock: false,
|
||||||
|
spellcheckEnabled: true,
|
||||||
|
keyboardShortcuts: defaultKeyboardShortcuts,
|
||||||
|
externalGoosed: {
|
||||||
|
enabled: false,
|
||||||
|
url: '',
|
||||||
|
secret: '',
|
||||||
|
},
|
||||||
|
|
||||||
|
// UI preferences
|
||||||
|
theme: 'light',
|
||||||
|
useSystemTheme: true,
|
||||||
|
responseStyle: 'concise',
|
||||||
|
showPricing: true,
|
||||||
|
sessionSharing: {
|
||||||
|
enabled: false,
|
||||||
|
baseUrl: '',
|
||||||
|
},
|
||||||
|
seenAnnouncementIds: [],
|
||||||
|
};
|
||||||
|
|
||||||
export function getKeyboardShortcuts(settings: Settings): KeyboardShortcuts {
|
export function getKeyboardShortcuts(settings: Settings): KeyboardShortcuts {
|
||||||
if (!settings.keyboardShortcuts && settings.globalShortcut !== undefined) {
|
if (!settings.keyboardShortcuts && settings.globalShortcut !== undefined) {
|
||||||
const focusShortcut = settings.globalShortcut;
|
const focusShortcut = settings.globalShortcut;
|
||||||
|
|||||||
Reference in New Issue
Block a user