需求:服务端统一 Agent Gateway 将设计任务状态流切换为 WebSocket,客户端需要实时展示生成任务并支持断线恢复。 实现:Electron Main 管理 Session、一次性 Ticket、WebSocket 心跳与游标续传,按关闭码回收会话;Renderer 继续通过本机 Host API 的 SSE 投影接收任务事件,并保留 REST 降级同步。 验证:typecheck、变更文件 ESLint、37 个聚焦测试及 build:vite 通过。
202 lines
5.1 KiB
TypeScript
202 lines
5.1 KiB
TypeScript
/**
|
|
* Persistent Storage
|
|
* Electron-store wrapper for application settings
|
|
*/
|
|
|
|
import { app } from 'electron';
|
|
import { randomUUID } from 'node:crypto';
|
|
import { resolveSupportedLanguage } from '../../shared/language';
|
|
|
|
// Lazy-load electron-store (ESM module)
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
let settingsStoreInstance: any = null;
|
|
|
|
/**
|
|
* Application settings schema
|
|
*/
|
|
export interface AppSettings {
|
|
// General
|
|
theme: 'light' | 'dark' | 'system';
|
|
language: string;
|
|
startMinimized: boolean;
|
|
launchAtStartup: boolean;
|
|
telemetryEnabled: boolean;
|
|
machineId: string;
|
|
agentGatewaySessionClientIds: Record<string, string>;
|
|
hasReportedInstall: boolean;
|
|
|
|
proxyEnabled: boolean;
|
|
proxyServer: string;
|
|
proxyHttpServer: string;
|
|
proxyHttpsServer: string;
|
|
proxyAllServer: string;
|
|
proxyBypassRules: string;
|
|
|
|
// Update
|
|
updateChannel: 'stable' | 'beta' | 'dev';
|
|
autoCheckUpdate: boolean;
|
|
autoDownloadUpdate: boolean;
|
|
skippedVersions: string[];
|
|
|
|
// UI State
|
|
sidebarCollapsed: boolean;
|
|
devModeUnlocked: boolean;
|
|
|
|
// Presets
|
|
selectedBundles: string[];
|
|
enabledSkills: string[];
|
|
disabledSkills: string[];
|
|
}
|
|
|
|
/**
|
|
* Default settings
|
|
*/
|
|
function getSystemLocale(): string {
|
|
const preferredLanguages = typeof app.getPreferredSystemLanguages === 'function'
|
|
? app.getPreferredSystemLanguages()
|
|
: [];
|
|
return preferredLanguages[0]
|
|
|| (typeof app.getLocale === 'function' ? app.getLocale() : '')
|
|
|| Intl.DateTimeFormat().resolvedOptions().locale
|
|
|| 'en';
|
|
}
|
|
|
|
function createDefaultSettings(): AppSettings {
|
|
return {
|
|
// General
|
|
theme: 'system',
|
|
language: resolveSupportedLanguage(getSystemLocale()),
|
|
startMinimized: false,
|
|
launchAtStartup: false,
|
|
telemetryEnabled: true,
|
|
machineId: '',
|
|
agentGatewaySessionClientIds: {},
|
|
hasReportedInstall: false,
|
|
|
|
proxyEnabled: false,
|
|
proxyServer: '',
|
|
proxyHttpServer: '',
|
|
proxyHttpsServer: '',
|
|
proxyAllServer: '',
|
|
proxyBypassRules: '<local>;localhost;127.0.0.1;::1',
|
|
|
|
// Update
|
|
updateChannel: 'stable',
|
|
autoCheckUpdate: true,
|
|
autoDownloadUpdate: false,
|
|
skippedVersions: [],
|
|
|
|
// UI State
|
|
sidebarCollapsed: false,
|
|
devModeUnlocked: false,
|
|
|
|
// Presets
|
|
selectedBundles: ['productivity', 'developer'],
|
|
enabledSkills: [],
|
|
disabledSkills: [],
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Get the settings store instance (lazy initialization)
|
|
*/
|
|
async function getSettingsStore() {
|
|
if (!settingsStoreInstance) {
|
|
const Store = (await import('electron-store')).default;
|
|
settingsStoreInstance = new Store<AppSettings>({
|
|
name: 'settings',
|
|
defaults: createDefaultSettings(),
|
|
});
|
|
}
|
|
return settingsStoreInstance;
|
|
}
|
|
|
|
/**
|
|
* Get a setting value
|
|
*/
|
|
export async function getSetting<K extends keyof AppSettings>(key: K): Promise<AppSettings[K]> {
|
|
const store = await getSettingsStore();
|
|
return store.get(key);
|
|
}
|
|
|
|
/**
|
|
* Set a setting value
|
|
*/
|
|
export async function setSetting<K extends keyof AppSettings>(
|
|
key: K,
|
|
value: AppSettings[K]
|
|
): Promise<void> {
|
|
const store = await getSettingsStore();
|
|
store.set(key, value);
|
|
}
|
|
|
|
let agentGatewaySessionIdMutation: Promise<void> = Promise.resolve();
|
|
|
|
async function mutateAgentGatewaySessionClientIds<T>(
|
|
mutation: (current: Record<string, string>) => Promise<T> | T,
|
|
): Promise<T> {
|
|
const result = agentGatewaySessionIdMutation.then(async () => {
|
|
const current = await getSetting('agentGatewaySessionClientIds');
|
|
return await mutation({ ...current });
|
|
});
|
|
agentGatewaySessionIdMutation = result.then(() => undefined, () => undefined);
|
|
return await result;
|
|
}
|
|
|
|
export function getOrCreateAgentGatewaySessionClientId(workspaceId: string): Promise<string> {
|
|
return mutateAgentGatewaySessionClientIds(async (current) => {
|
|
const existing = current[workspaceId]?.trim();
|
|
if (existing) return existing;
|
|
const created = `design-stream-${randomUUID()}`;
|
|
current[workspaceId] = created;
|
|
await setSetting('agentGatewaySessionClientIds', current);
|
|
return created;
|
|
});
|
|
}
|
|
|
|
export function rotateAgentGatewaySessionClientId(workspaceId: string): Promise<string> {
|
|
return mutateAgentGatewaySessionClientIds(async (current) => {
|
|
const created = `design-stream-${randomUUID()}`;
|
|
current[workspaceId] = created;
|
|
await setSetting('agentGatewaySessionClientIds', current);
|
|
return created;
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Get all settings
|
|
*/
|
|
export async function getAllSettings(): Promise<AppSettings> {
|
|
const store = await getSettingsStore();
|
|
return store.store;
|
|
}
|
|
|
|
/**
|
|
* Reset settings to defaults
|
|
*/
|
|
export async function resetSettings(): Promise<void> {
|
|
const store = await getSettingsStore();
|
|
store.clear();
|
|
}
|
|
|
|
/**
|
|
* Export settings to JSON
|
|
*/
|
|
export async function exportSettings(): Promise<string> {
|
|
const store = await getSettingsStore();
|
|
return JSON.stringify(store.store, null, 2);
|
|
}
|
|
|
|
/**
|
|
* Import settings from JSON
|
|
*/
|
|
export async function importSettings(json: string): Promise<void> {
|
|
try {
|
|
const settings = JSON.parse(json);
|
|
const store = await getSettingsStore();
|
|
store.set(settings);
|
|
} catch {
|
|
throw new Error('Invalid settings JSON');
|
|
}
|
|
}
|