2025-12-07 19:32:53 +02:00
|
|
|
import { getDesktopSettings, updateDesktopSettings as updateDesktopSettingsApi, isDesktopRuntime } from '@/lib/desktop';
|
|
|
|
|
import type { DesktopSettings } from '@/lib/desktop';
|
|
|
|
|
import { useUIStore } from '@/stores/useUIStore';
|
|
|
|
|
import { loadAppearancePreferences, applyAppearancePreferences } from '@/lib/appearancePersistence';
|
|
|
|
|
import { getRegisteredRuntimeAPIs } from '@/contexts/runtimeAPIRegistry';
|
|
|
|
|
|
|
|
|
|
const persistToLocalStorage = (settings: DesktopSettings) => {
|
|
|
|
|
if (typeof window === 'undefined') {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (settings.themeId) {
|
|
|
|
|
localStorage.setItem('selectedThemeId', settings.themeId);
|
|
|
|
|
}
|
|
|
|
|
if (settings.themeVariant) {
|
|
|
|
|
localStorage.setItem('selectedThemeVariant', settings.themeVariant);
|
|
|
|
|
}
|
|
|
|
|
if (settings.lightThemeId) {
|
|
|
|
|
localStorage.setItem('lightThemeId', settings.lightThemeId);
|
|
|
|
|
}
|
|
|
|
|
if (settings.darkThemeId) {
|
|
|
|
|
localStorage.setItem('darkThemeId', settings.darkThemeId);
|
|
|
|
|
}
|
|
|
|
|
if (typeof settings.useSystemTheme === 'boolean') {
|
|
|
|
|
localStorage.setItem('useSystemTheme', String(settings.useSystemTheme));
|
|
|
|
|
}
|
|
|
|
|
if (settings.lastDirectory) {
|
|
|
|
|
localStorage.setItem('lastDirectory', settings.lastDirectory);
|
|
|
|
|
}
|
|
|
|
|
if (settings.homeDirectory) {
|
|
|
|
|
localStorage.setItem('homeDirectory', settings.homeDirectory);
|
|
|
|
|
window.__OPENCHAMBER_HOME__ = settings.homeDirectory;
|
|
|
|
|
}
|
|
|
|
|
if (Array.isArray(settings.pinnedDirectories) && settings.pinnedDirectories.length > 0) {
|
|
|
|
|
localStorage.setItem('pinnedDirectories', JSON.stringify(settings.pinnedDirectories));
|
|
|
|
|
} else {
|
|
|
|
|
localStorage.removeItem('pinnedDirectories');
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
type PersistApi = {
|
|
|
|
|
hasHydrated?: () => boolean;
|
|
|
|
|
onFinishHydration?: (callback: () => void) => (() => void) | void;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const getPersistApi = (): PersistApi | undefined => {
|
|
|
|
|
const candidate = (useUIStore as unknown as { persist?: PersistApi }).persist;
|
|
|
|
|
if (candidate && typeof candidate === 'object') {
|
|
|
|
|
return candidate;
|
|
|
|
|
}
|
|
|
|
|
return undefined;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const getRuntimeSettingsAPI = () => getRegisteredRuntimeAPIs()?.settings ?? null;
|
|
|
|
|
|
|
|
|
|
const applyDesktopUiPreferences = (settings: DesktopSettings) => {
|
|
|
|
|
const store = useUIStore.getState();
|
|
|
|
|
|
|
|
|
|
if (typeof settings.showReasoningTraces === 'boolean' && settings.showReasoningTraces !== store.showReasoningTraces) {
|
|
|
|
|
store.setShowReasoningTraces(settings.showReasoningTraces);
|
|
|
|
|
}
|
2025-12-20 02:06:00 +02:00
|
|
|
if (typeof settings.autoDeleteEnabled === 'boolean' && settings.autoDeleteEnabled !== store.autoDeleteEnabled) {
|
|
|
|
|
store.setAutoDeleteEnabled(settings.autoDeleteEnabled);
|
|
|
|
|
}
|
|
|
|
|
if (typeof settings.autoDeleteAfterDays === 'number' && Number.isFinite(settings.autoDeleteAfterDays)) {
|
|
|
|
|
const normalized = Math.max(1, Math.min(365, settings.autoDeleteAfterDays));
|
|
|
|
|
if (normalized !== store.autoDeleteAfterDays) {
|
|
|
|
|
store.setAutoDeleteAfterDays(normalized);
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-12-07 19:32:53 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const sanitizeWebSettings = (payload: unknown): DesktopSettings | null => {
|
|
|
|
|
if (!payload || typeof payload !== 'object') {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const candidate = payload as Record<string, unknown>;
|
|
|
|
|
const result: DesktopSettings = {};
|
|
|
|
|
|
|
|
|
|
if (typeof candidate.themeId === 'string' && candidate.themeId.length > 0) {
|
|
|
|
|
result.themeId = candidate.themeId;
|
|
|
|
|
}
|
|
|
|
|
if (candidate.useSystemTheme === true || candidate.useSystemTheme === false) {
|
|
|
|
|
result.useSystemTheme = candidate.useSystemTheme;
|
|
|
|
|
}
|
|
|
|
|
if (typeof candidate.themeVariant === 'string' && (candidate.themeVariant === 'light' || candidate.themeVariant === 'dark')) {
|
|
|
|
|
result.themeVariant = candidate.themeVariant;
|
|
|
|
|
}
|
|
|
|
|
if (typeof candidate.lightThemeId === 'string' && candidate.lightThemeId.length > 0) {
|
|
|
|
|
result.lightThemeId = candidate.lightThemeId;
|
|
|
|
|
}
|
|
|
|
|
if (typeof candidate.darkThemeId === 'string' && candidate.darkThemeId.length > 0) {
|
|
|
|
|
result.darkThemeId = candidate.darkThemeId;
|
|
|
|
|
}
|
|
|
|
|
if (typeof candidate.lastDirectory === 'string' && candidate.lastDirectory.length > 0) {
|
|
|
|
|
result.lastDirectory = candidate.lastDirectory;
|
|
|
|
|
}
|
|
|
|
|
if (typeof candidate.homeDirectory === 'string' && candidate.homeDirectory.length > 0) {
|
|
|
|
|
result.homeDirectory = candidate.homeDirectory;
|
|
|
|
|
}
|
|
|
|
|
if (Array.isArray(candidate.approvedDirectories)) {
|
|
|
|
|
result.approvedDirectories = candidate.approvedDirectories.filter(
|
|
|
|
|
(entry): entry is string => typeof entry === 'string' && entry.length > 0
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
if (Array.isArray(candidate.securityScopedBookmarks)) {
|
|
|
|
|
result.securityScopedBookmarks = candidate.securityScopedBookmarks.filter(
|
|
|
|
|
(entry): entry is string => typeof entry === 'string' && entry.length > 0
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
if (Array.isArray(candidate.pinnedDirectories)) {
|
|
|
|
|
result.pinnedDirectories = Array.from(
|
|
|
|
|
new Set(
|
|
|
|
|
candidate.pinnedDirectories.filter((entry): entry is string => typeof entry === 'string' && entry.length > 0)
|
|
|
|
|
)
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
if (typeof candidate.showReasoningTraces === 'boolean') {
|
|
|
|
|
result.showReasoningTraces = candidate.showReasoningTraces;
|
|
|
|
|
}
|
2025-12-20 02:06:00 +02:00
|
|
|
if (typeof candidate.autoDeleteEnabled === 'boolean') {
|
|
|
|
|
result.autoDeleteEnabled = candidate.autoDeleteEnabled;
|
|
|
|
|
}
|
|
|
|
|
if (typeof candidate.autoDeleteAfterDays === 'number' && Number.isFinite(candidate.autoDeleteAfterDays)) {
|
|
|
|
|
result.autoDeleteAfterDays = candidate.autoDeleteAfterDays;
|
|
|
|
|
}
|
2025-12-26 04:38:38 +02:00
|
|
|
if (typeof candidate.defaultModel === 'string' && candidate.defaultModel.length > 0) {
|
|
|
|
|
result.defaultModel = candidate.defaultModel;
|
|
|
|
|
}
|
|
|
|
|
if (typeof candidate.defaultAgent === 'string' && candidate.defaultAgent.length > 0) {
|
|
|
|
|
result.defaultAgent = candidate.defaultAgent;
|
|
|
|
|
}
|
2025-12-07 19:32:53 +02:00
|
|
|
|
|
|
|
|
return result;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const fetchWebSettings = async (): Promise<DesktopSettings | null> => {
|
|
|
|
|
const runtimeSettings = getRuntimeSettingsAPI();
|
|
|
|
|
if (runtimeSettings) {
|
|
|
|
|
try {
|
|
|
|
|
const result = await runtimeSettings.load();
|
|
|
|
|
return sanitizeWebSettings(result.settings);
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.warn('Failed to load shared settings from runtime settings API:', error);
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
const response = await fetch('/api/config/settings', {
|
|
|
|
|
method: 'GET',
|
|
|
|
|
headers: { Accept: 'application/json' },
|
|
|
|
|
});
|
|
|
|
|
if (!response.ok) {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
const data = await response.json().catch(() => null);
|
|
|
|
|
return sanitizeWebSettings(data);
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.warn('Failed to load shared settings from server:', error);
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export const syncDesktopSettings = async (): Promise<void> => {
|
|
|
|
|
if (typeof window === 'undefined') {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const persistApi = getPersistApi();
|
|
|
|
|
|
|
|
|
|
const applySettings = (settings: DesktopSettings) => {
|
|
|
|
|
persistToLocalStorage(settings);
|
|
|
|
|
const apply = () => applyDesktopUiPreferences(settings);
|
|
|
|
|
|
|
|
|
|
if (persistApi?.hasHydrated?.()) {
|
|
|
|
|
apply();
|
|
|
|
|
} else {
|
|
|
|
|
apply();
|
|
|
|
|
if (persistApi?.onFinishHydration) {
|
|
|
|
|
const unsubscribe = persistApi.onFinishHydration(() => {
|
|
|
|
|
unsubscribe?.();
|
|
|
|
|
apply();
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (typeof window !== 'undefined') {
|
|
|
|
|
window.dispatchEvent(new CustomEvent<DesktopSettings>('openchamber:settings-synced', { detail: settings }));
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
const settings = isDesktopRuntime() ? await getDesktopSettings() : await fetchWebSettings();
|
|
|
|
|
if (settings) {
|
|
|
|
|
applySettings(settings);
|
|
|
|
|
}
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.warn('Failed to synchronise settings:', error);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export const updateDesktopSettings = async (changes: Partial<DesktopSettings>): Promise<void> => {
|
|
|
|
|
if (typeof window === 'undefined') {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (isDesktopRuntime()) {
|
|
|
|
|
try {
|
|
|
|
|
const updated = await updateDesktopSettingsApi(changes);
|
|
|
|
|
if (updated) {
|
|
|
|
|
persistToLocalStorage(updated);
|
|
|
|
|
applyDesktopUiPreferences(updated);
|
|
|
|
|
}
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.warn('Failed to update desktop settings:', error);
|
|
|
|
|
}
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const runtimeSettings = getRuntimeSettingsAPI();
|
|
|
|
|
if (runtimeSettings) {
|
|
|
|
|
try {
|
|
|
|
|
const updated = await runtimeSettings.save(changes);
|
|
|
|
|
if (updated) {
|
|
|
|
|
persistToLocalStorage(updated);
|
|
|
|
|
applyDesktopUiPreferences(updated);
|
|
|
|
|
}
|
|
|
|
|
return;
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.warn('Failed to update settings via runtime settings API:', error);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
const response = await fetch('/api/config/settings', {
|
|
|
|
|
method: 'PUT',
|
|
|
|
|
headers: {
|
|
|
|
|
'Content-Type': 'application/json',
|
|
|
|
|
Accept: 'application/json',
|
|
|
|
|
},
|
|
|
|
|
body: JSON.stringify(changes),
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (!response.ok) {
|
|
|
|
|
console.warn('Failed to update shared settings via API:', response.status, response.statusText);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const updated = (await response.json().catch(() => null)) as DesktopSettings | null;
|
|
|
|
|
if (updated) {
|
|
|
|
|
persistToLocalStorage(updated);
|
|
|
|
|
applyDesktopUiPreferences(updated);
|
|
|
|
|
}
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.warn('Failed to update shared settings via API:', error);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export const initializeAppearancePreferences = async (): Promise<void> => {
|
|
|
|
|
if (typeof window === 'undefined') {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const persistApi = getPersistApi();
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
const appearance = await loadAppearancePreferences();
|
|
|
|
|
if (!appearance) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const applyAppearance = () => applyAppearancePreferences(appearance);
|
|
|
|
|
|
|
|
|
|
if (persistApi?.hasHydrated?.()) {
|
|
|
|
|
applyAppearance();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
applyAppearance();
|
|
|
|
|
if (persistApi?.onFinishHydration) {
|
|
|
|
|
const unsubscribe = persistApi.onFinishHydration(() => {
|
|
|
|
|
unsubscribe?.();
|
|
|
|
|
applyAppearance();
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.warn('Failed to load appearance preferences:', error);
|
|
|
|
|
}
|
|
|
|
|
};
|