diff --git a/packages/shared/data/preferences.ts b/packages/shared/data/preferences.ts index 49e2cf4fc8..d9107bd706 100644 --- a/packages/shared/data/preferences.ts +++ b/packages/shared/data/preferences.ts @@ -1,6 +1,6 @@ /** * Auto-generated preferences configuration - * Generated at: 2025-08-10T12:46:51.544Z + * Generated at: 2025-08-15T03:23:46.568Z * * This file is automatically generated from classification.json * To update this file, modify classification.json and run: @@ -301,7 +301,7 @@ export interface PreferencesType { // redux/selectionStore/selectionEnabled 'feature.selection.enabled': boolean // redux/selectionStore/filterList - 'feature.selection.filter_list': unknown[] + 'feature.selection.filter_list': string[] // redux/selectionStore/filterMode 'feature.selection.filter_mode': string // redux/selectionStore/isFollowToolbar @@ -310,6 +310,34 @@ export interface PreferencesType { 'feature.selection.remember_win_size': boolean // redux/selectionStore/triggerMode 'feature.selection.trigger_mode': string + // redux/shortcuts/shortcuts.clear_topic + 'shortcut.chat.clear': Record + // redux/shortcuts/shortcuts.copy_last_message + 'shortcut.chat.copy_last_message': Record + // redux/shortcuts/shortcuts.search_message_in_chat + 'shortcut.chat.search_message': Record + // redux/shortcuts/shortcuts.toggle_new_context + 'shortcut.chat.toggle_new_context': Record + // redux/shortcuts/shortcuts.exit_fullscreen + 'shortcut.exit_fullscreen': Record + // redux/shortcuts/shortcuts.search_message + 'shortcut.global.search_message': Record + // redux/shortcuts/shortcuts.show_app + 'shortcut.main_window.show': Record + // redux/shortcuts/shortcuts.mini_window + 'shortcut.mini_window.show': Record + // redux/shortcuts/shortcuts.show_settings + 'shortcut.show_settings': Record + // redux/shortcuts/shortcuts.toggle_show_assistants + 'shortcut.toggle_show_assistants': Record + // redux/shortcuts/shortcuts.new_topic + 'shortcut.topic.new': Record + // redux/shortcuts/shortcuts.zoom_in + 'shortcut.zoom_in': Record + // redux/shortcuts/shortcuts.zoom_out + 'shortcut.zoom_out': Record + // redux/shortcuts/shortcuts.zoom_reset + 'shortcut.zoom_reset': Record // redux/settings/enableTopicNaming 'topic.naming.enabled': boolean // redux/settings/topicNamingPrompt @@ -481,6 +509,35 @@ export const DefaultPreferences: PreferencesType = { 'feature.selection.follow_toolbar': true, 'feature.selection.remember_win_size': false, 'feature.selection.trigger_mode': 'selected', + 'shortcut.chat.clear': { editable: true, enabled: true, key: ['CommandOrControl', 'L'], system: false }, + 'shortcut.chat.copy_last_message': { + editable: true, + enabled: false, + key: ['CommandOrControl', 'Shift', 'C'], + system: false + }, + 'shortcut.chat.search_message': { editable: true, enabled: true, key: ['CommandOrControl', 'F'], system: false }, + 'shortcut.chat.toggle_new_context': { + editable: true, + enabled: true, + key: ['CommandOrControl', 'K'], + system: false + }, + 'shortcut.exit_fullscreen': { editable: false, enabled: true, key: ['Escape'], system: true }, + 'shortcut.global.search_message': { + editable: true, + enabled: true, + key: ['CommandOrControl', 'Shift', 'F'], + system: false + }, + 'shortcut.main_window.show': { editable: true, enabled: true, key: [], system: true }, + 'shortcut.mini_window.show': { editable: true, enabled: false, key: ['CommandOrControl', 'E'], system: true }, + 'shortcut.show_settings': { editable: false, enabled: true, key: ['CommandOrControl', ','], system: true }, + 'shortcut.toggle_show_assistants': { editable: true, enabled: true, key: ['CommandOrControl', '['], system: false }, + 'shortcut.topic.new': { editable: true, enabled: true, key: ['CommandOrControl', 'N'], system: false }, + 'shortcut.zoom_in': { editable: false, enabled: true, key: ['CommandOrControl', '='], system: true }, + 'shortcut.zoom_out': { editable: false, enabled: true, key: ['CommandOrControl', '-'], system: true }, + 'shortcut.zoom_reset': { editable: false, enabled: true, key: ['CommandOrControl', '0'], system: true }, 'topic.naming.enabled': true, 'topic.naming.prompt': '', 'topic.pin_to_top': false, @@ -497,8 +554,8 @@ export const DefaultPreferences: PreferencesType = { /** * 生成统计: - * - 总配置项: 156 + * - 总配置项: 170 * - electronStore项: 2 - * - redux项: 154 + * - redux项: 168 * - localStorage项: 0 */ diff --git a/packages/shared/data/types.d.ts b/packages/shared/data/types.d.ts index ca6481fef3..38f8b9140a 100644 --- a/packages/shared/data/types.d.ts +++ b/packages/shared/data/types.d.ts @@ -2,3 +2,10 @@ import { PreferencesType } from './preferences' export type PreferenceDefaultScopeType = PreferencesType['default'] export type PreferenceKeyType = keyof PreferenceDefaultScopeType + +export type PreferenceShortcutType = { + key: string[] + editable: boolean + enabled: boolean + system: boolean +} diff --git a/src/main/data/PreferenceService.ts b/src/main/data/PreferenceService.ts index 92f30a9ee8..ee6fd338f5 100644 --- a/src/main/data/PreferenceService.ts +++ b/src/main/data/PreferenceService.ts @@ -94,6 +94,20 @@ export class PreferenceService { return this.cache[key] ?? DefaultPreferences.default[key] } + /** + * Get a single preference value from memory cache and subscribe to changes + * @param key - The preference key to get + * @param callback - The callback function to call when the preference changes + * @returns The current value of the preference + */ + public getAndSubscribeChange( + key: K, + callback: (newValue: PreferenceDefaultScopeType[K], oldValue?: PreferenceDefaultScopeType[K]) => void + ): PreferenceDefaultScopeType[K] { + const value = this.get(key) + this.subscribeChange(key, callback) + return value + } /** * Set a single preference value * Updates both database and memory cache, then broadcasts changes to all listeners @@ -261,11 +275,11 @@ export class PreferenceService { */ public subscribeChange( key: K, - callback: (newValue: PreferenceDefaultScopeType[K], oldValue: PreferenceDefaultScopeType[K]) => void + callback: (newValue: PreferenceDefaultScopeType[K], oldValue?: PreferenceDefaultScopeType[K]) => void ): () => void { const listener = (changedKey: string, newValue: any, oldValue: any) => { if (changedKey === key) { - callback(newValue, oldValue) + callback(newValue as PreferenceDefaultScopeType[K], oldValue as PreferenceDefaultScopeType[K]) } } diff --git a/src/main/services/SelectionService.ts b/src/main/services/SelectionService.ts index 060708bb4b..855cdc0bb4 100644 --- a/src/main/services/SelectionService.ts +++ b/src/main/services/SelectionService.ts @@ -1,3 +1,4 @@ +import { preferenceService } from '@data/PreferenceService' import { loggerService } from '@logger' import { SELECTION_FINETUNED_LIST, SELECTION_PREDEFINED_BLACKLIST } from '@main/configs/SelectionConfig' import { isDev, isMac, isWin } from '@main/constant' @@ -13,8 +14,6 @@ import type { } from 'selection-hook' import type { ActionItem } from '../../renderer/src/types/selectionTypes' -import { ConfigKeys, configManager } from './ConfigManager' -import storeSyncService from './StoreSyncService' const logger = loggerService.withContext('SelectionService') @@ -144,12 +143,13 @@ export class SelectionService { * Ensures UI elements scale properly with system DPI settings */ private initZoomFactor(): void { - const zoomFactor = configManager.getZoomFactor() + const zoomFactor = preferenceService.getAndSubscribeChange('app.zoom_factor', (zoomFactor: number) => { + this.setZoomFactor(zoomFactor) + }) + if (zoomFactor) { this.setZoomFactor(zoomFactor) } - - configManager.subscribe('ZoomFactor', this.setZoomFactor) } public setZoomFactor = (zoomFactor: number) => { @@ -157,51 +157,53 @@ export class SelectionService { } private initConfig(): void { - this.triggerMode = configManager.getSelectionAssistantTriggerMode() as TriggerMode - this.isFollowToolbar = configManager.getSelectionAssistantFollowToolbar() - this.isRemeberWinSize = configManager.getSelectionAssistantRemeberWinSize() - this.filterMode = configManager.getSelectionAssistantFilterMode() - this.filterList = configManager.getSelectionAssistantFilterList() + this.triggerMode = preferenceService.getAndSubscribeChange( + 'feature.selection.trigger_mode', + (triggerMode: string) => { + const oldTriggerMode = this.triggerMode as TriggerMode - this.setHookGlobalFilterMode(this.filterMode, this.filterList) - this.setHookFineTunedList() + this.triggerMode = triggerMode as TriggerMode + this.processTriggerMode() - configManager.subscribe(ConfigKeys.SelectionAssistantTriggerMode, (triggerMode: TriggerMode) => { - const oldTriggerMode = this.triggerMode - - this.triggerMode = triggerMode - this.processTriggerMode() - - //trigger mode changed, need to update the filter list - if (oldTriggerMode !== triggerMode) { - this.setHookGlobalFilterMode(this.filterMode, this.filterList) - } - }) - - configManager.subscribe(ConfigKeys.SelectionAssistantFollowToolbar, (isFollowToolbar: boolean) => { - this.isFollowToolbar = isFollowToolbar - }) - - configManager.subscribe(ConfigKeys.SelectionAssistantRemeberWinSize, (isRemeberWinSize: boolean) => { - this.isRemeberWinSize = isRemeberWinSize - //when off, reset the last action window size to default - if (!this.isRemeberWinSize) { - this.lastActionWindowSize = { - width: this.ACTION_WINDOW_WIDTH, - height: this.ACTION_WINDOW_HEIGHT + //trigger mode changed, need to update the filter list + if (oldTriggerMode !== triggerMode) { + this.setHookGlobalFilterMode(this.filterMode, this.filterList) } } - }) - - configManager.subscribe(ConfigKeys.SelectionAssistantFilterMode, (filterMode: string) => { + ) as TriggerMode + this.isFollowToolbar = preferenceService.getAndSubscribeChange( + 'feature.selection.follow_toolbar', + (followToolbar: boolean) => { + this.isFollowToolbar = followToolbar + } + ) + this.isRemeberWinSize = preferenceService.getAndSubscribeChange( + 'feature.selection.remember_win_size', + (rememberWinSize: boolean) => { + this.isRemeberWinSize = rememberWinSize + //when off, reset the last action window size to default + if (!this.isRemeberWinSize) { + this.lastActionWindowSize = { + width: this.ACTION_WINDOW_WIDTH, + height: this.ACTION_WINDOW_HEIGHT + } + } + } + ) + this.filterMode = preferenceService.getAndSubscribeChange('feature.selection.filter_mode', (filterMode: string) => { this.filterMode = filterMode this.setHookGlobalFilterMode(this.filterMode, this.filterList) }) + this.filterList = preferenceService.getAndSubscribeChange( + 'feature.selection.filter_list', + (filterList: string[]) => { + this.filterList = filterList + this.setHookGlobalFilterMode(this.filterMode, this.filterList) + } + ) - configManager.subscribe(ConfigKeys.SelectionAssistantFilterList, (filterList: string[]) => { - this.filterList = filterList - this.setHookGlobalFilterMode(this.filterMode, this.filterList) - }) + this.setHookGlobalFilterMode(this.filterMode, this.filterList) + this.setHookFineTunedList() } /** @@ -381,12 +383,9 @@ export class SelectionService { public toggleEnabled(enabled: boolean | undefined = undefined): void { if (!this.selectionHook) return - const newEnabled = enabled === undefined ? !configManager.getSelectionAssistantEnabled() : enabled + const newEnabled = enabled === undefined ? !preferenceService.get('feature.selection.enabled') : enabled - configManager.setSelectionAssistantEnabled(newEnabled) - - //sync the new enabled state to all renderer windows - storeSyncService.syncToRenderer('selectionStore/setSelectionEnabled', newEnabled) + preferenceService.set('feature.selection.enabled', newEnabled) } /** @@ -1462,27 +1461,27 @@ export class SelectionService { }) ipcMain.handle(IpcChannel.Selection_SetEnabled, (_, enabled: boolean) => { - configManager.setSelectionAssistantEnabled(enabled) + preferenceService.set('feature.selection.enabled', enabled) }) ipcMain.handle(IpcChannel.Selection_SetTriggerMode, (_, triggerMode: string) => { - configManager.setSelectionAssistantTriggerMode(triggerMode) + preferenceService.set('feature.selection.trigger_mode', triggerMode) }) ipcMain.handle(IpcChannel.Selection_SetFollowToolbar, (_, isFollowToolbar: boolean) => { - configManager.setSelectionAssistantFollowToolbar(isFollowToolbar) + preferenceService.set('feature.selection.follow_toolbar', isFollowToolbar) }) ipcMain.handle(IpcChannel.Selection_SetRemeberWinSize, (_, isRemeberWinSize: boolean) => { - configManager.setSelectionAssistantRemeberWinSize(isRemeberWinSize) + preferenceService.set('feature.selection.remember_win_size', isRemeberWinSize) }) ipcMain.handle(IpcChannel.Selection_SetFilterMode, (_, filterMode: string) => { - configManager.setSelectionAssistantFilterMode(filterMode) + preferenceService.set('feature.selection.filter_mode', filterMode) }) ipcMain.handle(IpcChannel.Selection_SetFilterList, (_, filterList: string[]) => { - configManager.setSelectionAssistantFilterList(filterList) + preferenceService.set('feature.selection.filter_list', filterList) }) // [macOS] only macOS has the available isFullscreen mode @@ -1533,7 +1532,7 @@ export class SelectionService { export function initSelectionService(): boolean { if (!isSupportedOS) return false - configManager.subscribe(ConfigKeys.SelectionAssistantEnabled, (enabled: boolean): void => { + const enabled = preferenceService.getAndSubscribeChange('feature.selection.enabled', (enabled: boolean): void => { //avoid closure const ss = SelectionService.getInstance() if (!ss) { @@ -1548,7 +1547,7 @@ export function initSelectionService(): boolean { } }) - if (!configManager.getSelectionAssistantEnabled()) return false + if (!enabled) return false const ss = SelectionService.getInstance() if (!ss) { diff --git a/src/renderer/src/pages/settings/SelectionAssistantSettings/SelectionAssistantSettings.tsx b/src/renderer/src/pages/settings/SelectionAssistantSettings/SelectionAssistantSettings.tsx index 1ca437aa17..b5616338e7 100644 --- a/src/renderer/src/pages/settings/SelectionAssistantSettings/SelectionAssistantSettings.tsx +++ b/src/renderer/src/pages/settings/SelectionAssistantSettings/SelectionAssistantSettings.tsx @@ -1,8 +1,9 @@ +import { usePreference } from '@data/hooks/usePreference' import { isMac, isWin } from '@renderer/config/constant' import { useTheme } from '@renderer/context/ThemeProvider' -import { useSelectionAssistant } from '@renderer/hooks/useSelectionAssistant' import { getSelectionDescriptionLabel } from '@renderer/i18n/label' import { FilterMode, TriggerMode } from '@renderer/types/selectionTypes' +import { ActionItem } from '@renderer/types/selectionTypes' import SelectionToolbar from '@renderer/windows/selection/toolbar/SelectionToolbar' import { Button, Radio, Row, Slider, Switch, Tooltip } from 'antd' import { CircleHelp, Edit2 } from 'lucide-react' @@ -27,30 +28,42 @@ import SelectionFilterListModal from './components/SelectionFilterListModal' const SelectionAssistantSettings: FC = () => { const { theme } = useTheme() const { t } = useTranslation() - const { - selectionEnabled, - triggerMode, - isCompact, - isAutoClose, - isAutoPin, - isFollowToolbar, - isRemeberWinSize, - actionItems, - actionWindowOpacity, - filterMode, - filterList, - setSelectionEnabled, - setTriggerMode, - setIsCompact, - setIsAutoClose, - setIsAutoPin, - setIsFollowToolbar, - setIsRemeberWinSize, - setActionWindowOpacity, - setActionItems, - setFilterMode, - setFilterList - } = useSelectionAssistant() + // const { + // selectionEnabled, + // triggerMode, + // isCompact, + // isAutoClose, + // isAutoPin, + // isFollowToolbar, + // isRemeberWinSize, + // actionItems, + // actionWindowOpacity, + // filterMode, + // filterList, + // setSelectionEnabled, + // setTriggerMode, + // setIsCompact, + // setIsAutoClose, + // setIsAutoPin, + // setIsFollowToolbar, + // setIsRemeberWinSize, + // setActionWindowOpacity, + // setActionItems, + // setFilterMode, + // setFilterList + // } = useSelectionAssistant() + + const [selectionEnabled, setSelectionEnabled] = usePreference('feature.selection.enabled') + const [triggerMode, setTriggerMode] = usePreference('feature.selection.trigger_mode') + const [isCompact, setIsCompact] = usePreference('feature.selection.compact') + const [isAutoClose, setIsAutoClose] = usePreference('feature.selection.auto_close') + const [isAutoPin, setIsAutoPin] = usePreference('feature.selection.auto_pin') + const [isFollowToolbar, setIsFollowToolbar] = usePreference('feature.selection.follow_toolbar') + const [isRemeberWinSize, setIsRemeberWinSize] = usePreference('feature.selection.remember_win_size') + const [actionWindowOpacity, setActionWindowOpacity] = usePreference('feature.selection.action_window_opacity') + const [filterMode, setFilterMode] = usePreference('feature.selection.filter_mode') + const [filterList, setFilterList] = usePreference('feature.selection.filter_list') + const [actionItems, setActionItems] = usePreference('feature.selection.action_items') const isSupportedOS = isWin || isMac @@ -229,7 +242,7 @@ const SelectionAssistantSettings: FC = () => { - + {t('selection.settings.advanced.title')}