feat(preferences): update preferences structure and enhance shortcut management

- Updated the preferences configuration with new shortcut definitions and types for better management.
- Introduced a method to get and subscribe to preference changes in the PreferenceService, improving reactivity.
- Refactored SelectionService to utilize the new preference management system, enhancing code clarity and maintainability.
- Updated SelectionAssistantSettings to use the new preference hooks for managing selection settings.
- Adjusted the generated preferences file to reflect the latest configuration changes.
This commit is contained in:
fullex 2025-08-15 12:54:24 +08:00
parent 30e6883333
commit e15005d1cf
5 changed files with 177 additions and 87 deletions

View File

@ -1,6 +1,6 @@
/** /**
* Auto-generated preferences configuration * 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 * This file is automatically generated from classification.json
* To update this file, modify classification.json and run: * To update this file, modify classification.json and run:
@ -301,7 +301,7 @@ export interface PreferencesType {
// redux/selectionStore/selectionEnabled // redux/selectionStore/selectionEnabled
'feature.selection.enabled': boolean 'feature.selection.enabled': boolean
// redux/selectionStore/filterList // redux/selectionStore/filterList
'feature.selection.filter_list': unknown[] 'feature.selection.filter_list': string[]
// redux/selectionStore/filterMode // redux/selectionStore/filterMode
'feature.selection.filter_mode': string 'feature.selection.filter_mode': string
// redux/selectionStore/isFollowToolbar // redux/selectionStore/isFollowToolbar
@ -310,6 +310,34 @@ export interface PreferencesType {
'feature.selection.remember_win_size': boolean 'feature.selection.remember_win_size': boolean
// redux/selectionStore/triggerMode // redux/selectionStore/triggerMode
'feature.selection.trigger_mode': string 'feature.selection.trigger_mode': string
// redux/shortcuts/shortcuts.clear_topic
'shortcut.chat.clear': Record<string, unknown>
// redux/shortcuts/shortcuts.copy_last_message
'shortcut.chat.copy_last_message': Record<string, unknown>
// redux/shortcuts/shortcuts.search_message_in_chat
'shortcut.chat.search_message': Record<string, unknown>
// redux/shortcuts/shortcuts.toggle_new_context
'shortcut.chat.toggle_new_context': Record<string, unknown>
// redux/shortcuts/shortcuts.exit_fullscreen
'shortcut.exit_fullscreen': Record<string, unknown>
// redux/shortcuts/shortcuts.search_message
'shortcut.global.search_message': Record<string, unknown>
// redux/shortcuts/shortcuts.show_app
'shortcut.main_window.show': Record<string, unknown>
// redux/shortcuts/shortcuts.mini_window
'shortcut.mini_window.show': Record<string, unknown>
// redux/shortcuts/shortcuts.show_settings
'shortcut.show_settings': Record<string, unknown>
// redux/shortcuts/shortcuts.toggle_show_assistants
'shortcut.toggle_show_assistants': Record<string, unknown>
// redux/shortcuts/shortcuts.new_topic
'shortcut.topic.new': Record<string, unknown>
// redux/shortcuts/shortcuts.zoom_in
'shortcut.zoom_in': Record<string, unknown>
// redux/shortcuts/shortcuts.zoom_out
'shortcut.zoom_out': Record<string, unknown>
// redux/shortcuts/shortcuts.zoom_reset
'shortcut.zoom_reset': Record<string, unknown>
// redux/settings/enableTopicNaming // redux/settings/enableTopicNaming
'topic.naming.enabled': boolean 'topic.naming.enabled': boolean
// redux/settings/topicNamingPrompt // redux/settings/topicNamingPrompt
@ -481,6 +509,35 @@ export const DefaultPreferences: PreferencesType = {
'feature.selection.follow_toolbar': true, 'feature.selection.follow_toolbar': true,
'feature.selection.remember_win_size': false, 'feature.selection.remember_win_size': false,
'feature.selection.trigger_mode': 'selected', '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.enabled': true,
'topic.naming.prompt': '', 'topic.naming.prompt': '',
'topic.pin_to_top': false, 'topic.pin_to_top': false,
@ -497,8 +554,8 @@ export const DefaultPreferences: PreferencesType = {
/** /**
* : * :
* - 总配置项: 156 * - 总配置项: 170
* - electronStore项: 2 * - electronStore项: 2
* - redux项: 154 * - redux项: 168
* - localStorage项: 0 * - localStorage项: 0
*/ */

View File

@ -2,3 +2,10 @@ import { PreferencesType } from './preferences'
export type PreferenceDefaultScopeType = PreferencesType['default'] export type PreferenceDefaultScopeType = PreferencesType['default']
export type PreferenceKeyType = keyof PreferenceDefaultScopeType export type PreferenceKeyType = keyof PreferenceDefaultScopeType
export type PreferenceShortcutType = {
key: string[]
editable: boolean
enabled: boolean
system: boolean
}

View File

@ -94,6 +94,20 @@ export class PreferenceService {
return this.cache[key] ?? DefaultPreferences.default[key] 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<K extends PreferenceKeyType>(
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 * Set a single preference value
* Updates both database and memory cache, then broadcasts changes to all listeners * Updates both database and memory cache, then broadcasts changes to all listeners
@ -261,11 +275,11 @@ export class PreferenceService {
*/ */
public subscribeChange<K extends PreferenceKeyType>( public subscribeChange<K extends PreferenceKeyType>(
key: K, key: K,
callback: (newValue: PreferenceDefaultScopeType[K], oldValue: PreferenceDefaultScopeType[K]) => void callback: (newValue: PreferenceDefaultScopeType[K], oldValue?: PreferenceDefaultScopeType[K]) => void
): () => void { ): () => void {
const listener = (changedKey: string, newValue: any, oldValue: any) => { const listener = (changedKey: string, newValue: any, oldValue: any) => {
if (changedKey === key) { if (changedKey === key) {
callback(newValue, oldValue) callback(newValue as PreferenceDefaultScopeType[K], oldValue as PreferenceDefaultScopeType[K])
} }
} }

View File

@ -1,3 +1,4 @@
import { preferenceService } from '@data/PreferenceService'
import { loggerService } from '@logger' import { loggerService } from '@logger'
import { SELECTION_FINETUNED_LIST, SELECTION_PREDEFINED_BLACKLIST } from '@main/configs/SelectionConfig' import { SELECTION_FINETUNED_LIST, SELECTION_PREDEFINED_BLACKLIST } from '@main/configs/SelectionConfig'
import { isDev, isMac, isWin } from '@main/constant' import { isDev, isMac, isWin } from '@main/constant'
@ -13,8 +14,6 @@ import type {
} from 'selection-hook' } from 'selection-hook'
import type { ActionItem } from '../../renderer/src/types/selectionTypes' import type { ActionItem } from '../../renderer/src/types/selectionTypes'
import { ConfigKeys, configManager } from './ConfigManager'
import storeSyncService from './StoreSyncService'
const logger = loggerService.withContext('SelectionService') const logger = loggerService.withContext('SelectionService')
@ -144,12 +143,13 @@ export class SelectionService {
* Ensures UI elements scale properly with system DPI settings * Ensures UI elements scale properly with system DPI settings
*/ */
private initZoomFactor(): void { private initZoomFactor(): void {
const zoomFactor = configManager.getZoomFactor() const zoomFactor = preferenceService.getAndSubscribeChange('app.zoom_factor', (zoomFactor: number) => {
this.setZoomFactor(zoomFactor)
})
if (zoomFactor) { if (zoomFactor) {
this.setZoomFactor(zoomFactor) this.setZoomFactor(zoomFactor)
} }
configManager.subscribe('ZoomFactor', this.setZoomFactor)
} }
public setZoomFactor = (zoomFactor: number) => { public setZoomFactor = (zoomFactor: number) => {
@ -157,51 +157,53 @@ export class SelectionService {
} }
private initConfig(): void { private initConfig(): void {
this.triggerMode = configManager.getSelectionAssistantTriggerMode() as TriggerMode this.triggerMode = preferenceService.getAndSubscribeChange(
this.isFollowToolbar = configManager.getSelectionAssistantFollowToolbar() 'feature.selection.trigger_mode',
this.isRemeberWinSize = configManager.getSelectionAssistantRemeberWinSize() (triggerMode: string) => {
this.filterMode = configManager.getSelectionAssistantFilterMode() const oldTriggerMode = this.triggerMode as TriggerMode
this.filterList = configManager.getSelectionAssistantFilterList()
this.setHookGlobalFilterMode(this.filterMode, this.filterList) this.triggerMode = triggerMode as TriggerMode
this.setHookFineTunedList() this.processTriggerMode()
configManager.subscribe(ConfigKeys.SelectionAssistantTriggerMode, (triggerMode: TriggerMode) => { //trigger mode changed, need to update the filter list
const oldTriggerMode = this.triggerMode if (oldTriggerMode !== triggerMode) {
this.setHookGlobalFilterMode(this.filterMode, this.filterList)
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
} }
} }
}) ) as TriggerMode
this.isFollowToolbar = preferenceService.getAndSubscribeChange(
configManager.subscribe(ConfigKeys.SelectionAssistantFilterMode, (filterMode: string) => { '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.filterMode = filterMode
this.setHookGlobalFilterMode(this.filterMode, this.filterList) 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.setHookGlobalFilterMode(this.filterMode, this.filterList)
this.filterList = filterList this.setHookFineTunedList()
this.setHookGlobalFilterMode(this.filterMode, this.filterList)
})
} }
/** /**
@ -381,12 +383,9 @@ export class SelectionService {
public toggleEnabled(enabled: boolean | undefined = undefined): void { public toggleEnabled(enabled: boolean | undefined = undefined): void {
if (!this.selectionHook) return if (!this.selectionHook) return
const newEnabled = enabled === undefined ? !configManager.getSelectionAssistantEnabled() : enabled const newEnabled = enabled === undefined ? !preferenceService.get('feature.selection.enabled') : enabled
configManager.setSelectionAssistantEnabled(newEnabled) preferenceService.set('feature.selection.enabled', newEnabled)
//sync the new enabled state to all renderer windows
storeSyncService.syncToRenderer('selectionStore/setSelectionEnabled', newEnabled)
} }
/** /**
@ -1462,27 +1461,27 @@ export class SelectionService {
}) })
ipcMain.handle(IpcChannel.Selection_SetEnabled, (_, enabled: boolean) => { ipcMain.handle(IpcChannel.Selection_SetEnabled, (_, enabled: boolean) => {
configManager.setSelectionAssistantEnabled(enabled) preferenceService.set('feature.selection.enabled', enabled)
}) })
ipcMain.handle(IpcChannel.Selection_SetTriggerMode, (_, triggerMode: string) => { ipcMain.handle(IpcChannel.Selection_SetTriggerMode, (_, triggerMode: string) => {
configManager.setSelectionAssistantTriggerMode(triggerMode) preferenceService.set('feature.selection.trigger_mode', triggerMode)
}) })
ipcMain.handle(IpcChannel.Selection_SetFollowToolbar, (_, isFollowToolbar: boolean) => { ipcMain.handle(IpcChannel.Selection_SetFollowToolbar, (_, isFollowToolbar: boolean) => {
configManager.setSelectionAssistantFollowToolbar(isFollowToolbar) preferenceService.set('feature.selection.follow_toolbar', isFollowToolbar)
}) })
ipcMain.handle(IpcChannel.Selection_SetRemeberWinSize, (_, isRemeberWinSize: boolean) => { 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) => { ipcMain.handle(IpcChannel.Selection_SetFilterMode, (_, filterMode: string) => {
configManager.setSelectionAssistantFilterMode(filterMode) preferenceService.set('feature.selection.filter_mode', filterMode)
}) })
ipcMain.handle(IpcChannel.Selection_SetFilterList, (_, filterList: string[]) => { 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 // [macOS] only macOS has the available isFullscreen mode
@ -1533,7 +1532,7 @@ export class SelectionService {
export function initSelectionService(): boolean { export function initSelectionService(): boolean {
if (!isSupportedOS) return false if (!isSupportedOS) return false
configManager.subscribe(ConfigKeys.SelectionAssistantEnabled, (enabled: boolean): void => { const enabled = preferenceService.getAndSubscribeChange('feature.selection.enabled', (enabled: boolean): void => {
//avoid closure //avoid closure
const ss = SelectionService.getInstance() const ss = SelectionService.getInstance()
if (!ss) { if (!ss) {
@ -1548,7 +1547,7 @@ export function initSelectionService(): boolean {
} }
}) })
if (!configManager.getSelectionAssistantEnabled()) return false if (!enabled) return false
const ss = SelectionService.getInstance() const ss = SelectionService.getInstance()
if (!ss) { if (!ss) {

View File

@ -1,8 +1,9 @@
import { usePreference } from '@data/hooks/usePreference'
import { isMac, isWin } from '@renderer/config/constant' import { isMac, isWin } from '@renderer/config/constant'
import { useTheme } from '@renderer/context/ThemeProvider' import { useTheme } from '@renderer/context/ThemeProvider'
import { useSelectionAssistant } from '@renderer/hooks/useSelectionAssistant'
import { getSelectionDescriptionLabel } from '@renderer/i18n/label' import { getSelectionDescriptionLabel } from '@renderer/i18n/label'
import { FilterMode, TriggerMode } from '@renderer/types/selectionTypes' import { FilterMode, TriggerMode } from '@renderer/types/selectionTypes'
import { ActionItem } from '@renderer/types/selectionTypes'
import SelectionToolbar from '@renderer/windows/selection/toolbar/SelectionToolbar' import SelectionToolbar from '@renderer/windows/selection/toolbar/SelectionToolbar'
import { Button, Radio, Row, Slider, Switch, Tooltip } from 'antd' import { Button, Radio, Row, Slider, Switch, Tooltip } from 'antd'
import { CircleHelp, Edit2 } from 'lucide-react' import { CircleHelp, Edit2 } from 'lucide-react'
@ -27,30 +28,42 @@ import SelectionFilterListModal from './components/SelectionFilterListModal'
const SelectionAssistantSettings: FC = () => { const SelectionAssistantSettings: FC = () => {
const { theme } = useTheme() const { theme } = useTheme()
const { t } = useTranslation() const { t } = useTranslation()
const { // const {
selectionEnabled, // selectionEnabled,
triggerMode, // triggerMode,
isCompact, // isCompact,
isAutoClose, // isAutoClose,
isAutoPin, // isAutoPin,
isFollowToolbar, // isFollowToolbar,
isRemeberWinSize, // isRemeberWinSize,
actionItems, // actionItems,
actionWindowOpacity, // actionWindowOpacity,
filterMode, // filterMode,
filterList, // filterList,
setSelectionEnabled, // setSelectionEnabled,
setTriggerMode, // setTriggerMode,
setIsCompact, // setIsCompact,
setIsAutoClose, // setIsAutoClose,
setIsAutoPin, // setIsAutoPin,
setIsFollowToolbar, // setIsFollowToolbar,
setIsRemeberWinSize, // setIsRemeberWinSize,
setActionWindowOpacity, // setActionWindowOpacity,
setActionItems, // setActionItems,
setFilterMode, // setFilterMode,
setFilterList // setFilterList
} = useSelectionAssistant() // } = 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 const isSupportedOS = isWin || isMac
@ -229,7 +242,7 @@ const SelectionAssistantSettings: FC = () => {
</SettingRow> </SettingRow>
</SettingGroup> </SettingGroup>
<SelectionActionsList actionItems={actionItems} setActionItems={setActionItems} /> <SelectionActionsList actionItems={actionItems as ActionItem[]} setActionItems={setActionItems} />
<SettingGroup theme={theme}> <SettingGroup theme={theme}>
<SettingTitle>{t('selection.settings.advanced.title')}</SettingTitle> <SettingTitle>{t('selection.settings.advanced.title')}</SettingTitle>