diff --git a/packages/shared/IpcChannel.ts b/packages/shared/IpcChannel.ts index 2ebdf9f15b..47eb320efb 100644 --- a/packages/shared/IpcChannel.ts +++ b/packages/shared/IpcChannel.ts @@ -319,7 +319,7 @@ export enum IpcChannel { // Data: Preference Preference_Get = 'preference:get', Preference_Set = 'preference:set', - Preference_GetMultiple = 'preference:get-multiple', + Preference_GetMultipleRaw = 'preference:get-multiple-raw', Preference_SetMultiple = 'preference:set-multiple', Preference_GetAll = 'preference:get-all', Preference_Subscribe = 'preference:subscribe', diff --git a/packages/shared/data/preference/preferenceTypes.ts b/packages/shared/data/preference/preferenceTypes.ts index 182504e4dd..0209d09888 100644 --- a/packages/shared/data/preference/preferenceTypes.ts +++ b/packages/shared/data/preference/preferenceTypes.ts @@ -3,6 +3,13 @@ import type { PreferenceSchemas } from './preferenceSchemas' export type PreferenceDefaultScopeType = PreferenceSchemas['default'] export type PreferenceKeyType = keyof PreferenceDefaultScopeType +/** + * Result type for getMultipleRaw - maps requested keys to their values or undefined + */ +export type MultiPreferencesResultType = { + [P in K]: PreferenceDefaultScopeType[P] | undefined +} + export type PreferenceUpdateOptions = { optimistic: boolean } diff --git a/src/main/data/PreferenceService.ts b/src/main/data/PreferenceService.ts index 9f25964979..4a934e64a5 100644 --- a/src/main/data/PreferenceService.ts +++ b/src/main/data/PreferenceService.ts @@ -1,7 +1,11 @@ import { dbService } from '@data/db/DbService' import { loggerService } from '@logger' import { DefaultPreferences } from '@shared/data/preference/preferenceSchemas' -import type { PreferenceDefaultScopeType, PreferenceKeyType } from '@shared/data/preference/preferenceTypes' +import type { + MultiPreferencesResultType, + PreferenceDefaultScopeType, + PreferenceKeyType +} from '@shared/data/preference/preferenceTypes' import { IpcChannel } from '@shared/IpcChannel' import { and, eq } from 'drizzle-orm' import { BrowserWindow, ipcMain } from 'electron' @@ -118,8 +122,6 @@ class PreferenceNotifier { } } -type MultiPreferencesResultType = { [P in K]: PreferenceDefaultScopeType[P] | undefined } - const DefaultScope = 'default' /** * PreferenceService manages preference data storage and synchronization across multiple windows @@ -153,6 +155,7 @@ export class PreferenceService { /** * Get the singleton instance of PreferenceService + * @returns The singleton PreferenceService instance */ public static getInstance(): PreferenceService { if (!PreferenceService.instance) { @@ -164,6 +167,7 @@ export class PreferenceService { /** * Initialize preference cache from database * Should be called once at application startup + * @returns Promise that resolves when initialization completes */ public async initialize(): Promise { if (this.initialized) { @@ -194,6 +198,8 @@ export class PreferenceService { /** * Get a single preference value from memory cache * Fast synchronous access - no database queries after initialization + * @param key The preference key to retrieve + * @returns The preference value with defaults applied */ public get(key: K): PreferenceDefaultScopeType[K] { if (!this.initialized) { @@ -208,6 +214,9 @@ export class PreferenceService { * Set a single preference value * Updates both database and memory cache, then broadcasts changes to all listeners * Optimized to skip database writes and notifications when value hasn't changed + * @param key The preference key to update + * @param value The new value to set + * @returns Promise that resolves when update completes */ public async set(key: K, value: PreferenceDefaultScopeType[K]): Promise { try { @@ -247,8 +256,10 @@ export class PreferenceService { /** * Get multiple preferences at once from memory cache * Fast synchronous access - no database queries + * @param keys Array of preference keys to retrieve + * @returns Object with preference values for requested keys */ - public getMultiple(keys: K[]): MultiPreferencesResultType { + public getMultipleRaw(keys: K[]): MultiPreferencesResultType { if (!this.initialized) { logger.warn('Preference cache not initialized, returning defaults for multiple keys') const output: MultiPreferencesResultType = {} as MultiPreferencesResultType @@ -274,10 +285,38 @@ export class PreferenceService { return output } + /** + * Get multiple preferences with custom key mapping + * @param keys Object mapping local names to preference keys + * @returns Object with mapped preference values + * @example + * ```typescript + * const { host, port } = preferenceService.getMultiple({ + * host: 'feature.csaas.host', + * port: 'feature.csaas.port' + * }) + * ``` + */ + public getMultiple>( + keys: T + ): { [P in keyof T]: PreferenceDefaultScopeType[T[P]] } { + const preferenceKeys = Object.values(keys) as PreferenceKeyType[] + const values = this.getMultipleRaw(preferenceKeys) + const result = {} as { [P in keyof T]: PreferenceDefaultScopeType[T[P]] } + + for (const key in keys) { + result[key] = values[keys[key]]! + } + + return result + } + /** * Set multiple preferences at once * Updates both database and memory cache in a transaction, then broadcasts changes * Optimized to skip unchanged values and reduce database operations + * @param updates Object containing preference key-value pairs to update + * @returns Promise that resolves when all updates complete */ public async setMultiple(updates: Partial): Promise { try { @@ -345,6 +384,8 @@ export class PreferenceService { /** * Subscribe a window to preference changes * Window will receive notifications for specified keys + * @param windowId The ID of the BrowserWindow to subscribe + * @param keys Array of preference keys to subscribe to */ public subscribeForWindow(windowId: number, keys: string[]): void { if (!this.subscriptions.has(windowId)) { @@ -359,6 +400,7 @@ export class PreferenceService { /** * Unsubscribe a window from preference changes + * @param windowId The ID of the BrowserWindow to unsubscribe */ public unsubscribeForWindow(windowId: number): void { this.subscriptions.delete(windowId) @@ -369,7 +411,9 @@ export class PreferenceService { /** * Subscribe to preference changes in main process - * Returns unsubscribe function for cleanup + * @param key The preference key to watch for changes + * @param callback Function to call when the preference changes + * @returns Unsubscribe function for cleanup */ public subscribeChange( key: K, @@ -386,7 +430,9 @@ export class PreferenceService { /** * Subscribe to multiple preference changes in main process - * Returns unsubscribe function for cleanup + * @param keys Array of preference keys to watch for changes + * @param callback Function to call when any of the preferences change + * @returns Unsubscribe function for cleanup */ public subscribeMultipleChanges( keys: PreferenceKeyType[], @@ -417,6 +463,7 @@ export class PreferenceService { /** * Get main process listener count for debugging + * @returns Total number of change listeners */ public getChangeListenerCount(): number { return this.notifier.getTotalSubscriptionCount() @@ -424,6 +471,8 @@ export class PreferenceService { /** * Get subscription count for a specific preference key + * @param key The preference key to check + * @returns Number of listeners subscribed to this key */ public getKeyListenerCount(key: PreferenceKeyType): number { return this.notifier.getKeySubscriptionCount(key) @@ -431,6 +480,7 @@ export class PreferenceService { /** * Get all subscribed preference keys + * @returns Array of preference keys that have active subscriptions */ public getSubscribedKeys(): string[] { return this.notifier.getSubscribedKeys() @@ -438,6 +488,7 @@ export class PreferenceService { /** * Get detailed subscription statistics for debugging + * @returns Record mapping preference keys to their listener counts */ public getSubscriptionStats(): Record { return this.notifier.getSubscriptionStats() @@ -446,6 +497,10 @@ export class PreferenceService { /** * Unified notification method for both main and renderer processes * Broadcasts preference changes to main process listeners and subscribed renderer windows + * @param key The preference key that changed + * @param value The new value + * @param oldValue The previous value + * @returns Promise that resolves when all notifications are sent */ private async notifyChange(key: string, value: any, oldValue?: any): Promise { // 1. Notify main process listeners @@ -512,6 +567,7 @@ export class PreferenceService { /** * Get all preferences from memory cache * Returns complete preference object for bulk operations + * @returns Complete preference object with all values */ public getAll(): PreferenceDefaultScopeType { if (!this.initialized) { @@ -523,7 +579,8 @@ export class PreferenceService { } /** - * Get all current subscriptions (for debugging) + * Get all current window subscriptions (for debugging) + * @returns Map of window IDs to their subscribed preference keys */ public getSubscriptions(): Map> { return new Map(this.subscriptions) @@ -548,6 +605,9 @@ export class PreferenceService { /** * Deep equality check for preference values * Handles primitives, arrays, and plain objects + * @param a First value to compare + * @param b Second value to compare + * @returns True if values are deeply equal, false otherwise */ private isEqual(a: any, b: any): boolean { // Handle strict equality (primitives, same reference) @@ -603,8 +663,8 @@ export class PreferenceService { } ) - ipcMain.handle(IpcChannel.Preference_GetMultiple, (_, keys: PreferenceKeyType[]) => { - return instance.getMultiple(keys) + ipcMain.handle(IpcChannel.Preference_GetMultipleRaw, (_, keys: PreferenceKeyType[]) => { + return instance.getMultipleRaw(keys) }) ipcMain.handle(IpcChannel.Preference_SetMultiple, async (_, updates: Partial) => { diff --git a/src/main/services/ApiServerService.ts b/src/main/services/ApiServerService.ts index f70aab162e..6342ac74fc 100644 --- a/src/main/services/ApiServerService.ts +++ b/src/main/services/ApiServerService.ts @@ -61,7 +61,7 @@ export class ApiServerService { * Get current API server configuration from preference service */ getCurrentConfig(): ApiServerConfig { - const config = preferenceService.getMultiple([ + const config = preferenceService.getMultipleRaw([ 'feature.csaas.enabled', 'feature.csaas.host', 'feature.csaas.port', diff --git a/src/main/services/agents/services/claudecode/index.ts b/src/main/services/agents/services/claudecode/index.ts index 40e972f8a1..cf7d00f510 100644 --- a/src/main/services/agents/services/claudecode/index.ts +++ b/src/main/services/agents/services/claudecode/index.ts @@ -101,7 +101,7 @@ class ClaudeCodeService implements AgentServiceInterface { return aiStream } - const apiConfig = preferenceService.getMultiple([ + const apiConfig = preferenceService.getMultipleRaw([ 'feature.csaas.host', 'feature.csaas.port', 'feature.csaas.api_key' diff --git a/src/preload/index.ts b/src/preload/index.ts index 41e34b285e..2cc68b4315 100644 --- a/src/preload/index.ts +++ b/src/preload/index.ts @@ -7,6 +7,7 @@ import type { LogLevel, LogSourceWithContext } from '@shared/config/logger' import type { FileChangeEvent, WebviewKeyEvent } from '@shared/config/types' import type { CacheSyncMessage } from '@shared/data/cache/cacheTypes' import type { + MultiPreferencesResultType, PreferenceDefaultScopeType, PreferenceKeyType, SelectionActionItem @@ -553,8 +554,8 @@ const api = { ipcRenderer.invoke(IpcChannel.Preference_Get, key), set: (key: K, value: PreferenceDefaultScopeType[K]): Promise => ipcRenderer.invoke(IpcChannel.Preference_Set, key, value), - getMultiple: (keys: PreferenceKeyType[]): Promise> => - ipcRenderer.invoke(IpcChannel.Preference_GetMultiple, keys), + getMultipleRaw: (keys: K[]): Promise> => + ipcRenderer.invoke(IpcChannel.Preference_GetMultipleRaw, keys), setMultiple: (updates: Partial) => ipcRenderer.invoke(IpcChannel.Preference_SetMultiple, updates), getAll: (): Promise => ipcRenderer.invoke(IpcChannel.Preference_GetAll), diff --git a/src/renderer/src/data/PreferenceService.ts b/src/renderer/src/data/PreferenceService.ts index d106f5ddce..6032c52fa0 100644 --- a/src/renderer/src/data/PreferenceService.ts +++ b/src/renderer/src/data/PreferenceService.ts @@ -1,6 +1,7 @@ import { loggerService } from '@logger' import { DefaultPreferences } from '@shared/data/preference/preferenceSchemas' import type { + MultiPreferencesResultType, PreferenceDefaultScopeType, PreferenceKeyType, PreferenceUpdateOptions @@ -241,9 +242,9 @@ export class PreferenceService { /** * Get multiple preferences at once with caching and auto-subscription * @param keys Array of preference keys to retrieve - * @returns Promise resolving to partial object with preference values + * @returns Promise resolving to object with preference values for requested keys */ - public async getMultipleRaw(keys: PreferenceKeyType[]): Promise> { + public async getMultipleRaw(keys: K[]): Promise> { // Check which keys are already cached const cachedResults: Partial = {} const uncachedKeys: PreferenceKeyType[] = [] @@ -260,7 +261,7 @@ export class PreferenceService { // Fetch uncached keys from main process if (uncachedKeys.length > 0) { try { - const uncachedResults = await window.api.preference.getMultiple(uncachedKeys) + const uncachedResults = await window.api.preference.getMultipleRaw(uncachedKeys) // Update cache with new results for (const [key, value] of Object.entries(uncachedResults)) { @@ -271,7 +272,7 @@ export class PreferenceService { await this.subscribeToKeyInternal([key as PreferenceKeyType]) } - return { ...cachedResults, ...uncachedResults } + return { ...cachedResults, ...uncachedResults } as MultiPreferencesResultType } catch (error) { logger.error('Failed to get multiple preferences:', error as Error) @@ -283,11 +284,11 @@ export class PreferenceService { } } - return { ...cachedResults, ...defaultResults } + return { ...cachedResults, ...defaultResults } as MultiPreferencesResultType } } - return cachedResults + return cachedResults as MultiPreferencesResultType } /** diff --git a/src/renderer/src/pages/settings/ToolSettings/ApiServerSettings/ApiServerSettings.tsx b/src/renderer/src/pages/settings/ToolSettings/ApiServerSettings/ApiServerSettings.tsx index 1d71c768b5..4d888a52c7 100644 --- a/src/renderer/src/pages/settings/ToolSettings/ApiServerSettings/ApiServerSettings.tsx +++ b/src/renderer/src/pages/settings/ToolSettings/ApiServerSettings/ApiServerSettings.tsx @@ -57,7 +57,6 @@ const ApiServerSettings: FC = () => { return `cs-sk-${uuidv4()}` } - const regenerateApiKey = () => { setApiServerConfig({ apiKey: generateApiKey() }) window.toast.success(t('apiServer.messages.apiKeyRegenerated'))