Initial public release
This commit is contained in:
@@ -0,0 +1,270 @@
|
||||
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);
|
||||
}
|
||||
};
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user