mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2025-12-24 02:20:10 +08:00
feat(preferences): enhance selection management with type safety and refactor preferences structure
- Introduced new types for selection management: `SelectionActionItem`, `SelectionFilterMode`, and `SelectionTriggerMode` to improve type safety across the application. - Updated `PreferencesType` to utilize these new types, ensuring better consistency and clarity in preference definitions. - Refactored `SelectionService` and related components to adopt the new types, enhancing the handling of selection actions and preferences. - Cleaned up deprecated types and improved the overall structure of preference management, aligning with recent updates in the preference system.
This commit is contained in:
parent
087e825086
commit
b15778b16b
@ -1,3 +1,5 @@
|
|||||||
|
import type { SelectionActionItem, SelectionFilterMode, SelectionTriggerMode } from './types'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Auto-generated preferences configuration
|
* Auto-generated preferences configuration
|
||||||
* Generated at: 2025-08-15T03:23:46.568Z
|
* Generated at: 2025-08-15T03:23:46.568Z
|
||||||
@ -13,7 +15,6 @@
|
|||||||
"interfaces": { "order": "alphabetically" },
|
"interfaces": { "order": "alphabetically" },
|
||||||
"typeLiterals": { "order": "alphabetically" }
|
"typeLiterals": { "order": "alphabetically" }
|
||||||
}] */
|
}] */
|
||||||
|
|
||||||
export interface PreferencesType {
|
export interface PreferencesType {
|
||||||
default: {
|
default: {
|
||||||
// redux/settings/enableDeveloperMode
|
// redux/settings/enableDeveloperMode
|
||||||
@ -289,7 +290,7 @@ export interface PreferencesType {
|
|||||||
// redux/settings/readClipboardAtStartup
|
// redux/settings/readClipboardAtStartup
|
||||||
'feature.quick_assistant.read_clipboard_at_startup': boolean
|
'feature.quick_assistant.read_clipboard_at_startup': boolean
|
||||||
// redux/selectionStore/actionItems
|
// redux/selectionStore/actionItems
|
||||||
'feature.selection.action_items': unknown[]
|
'feature.selection.action_items': SelectionActionItem[]
|
||||||
// redux/selectionStore/actionWindowOpacity
|
// redux/selectionStore/actionWindowOpacity
|
||||||
'feature.selection.action_window_opacity': number
|
'feature.selection.action_window_opacity': number
|
||||||
// redux/selectionStore/isAutoClose
|
// redux/selectionStore/isAutoClose
|
||||||
@ -303,13 +304,13 @@ export interface PreferencesType {
|
|||||||
// redux/selectionStore/filterList
|
// redux/selectionStore/filterList
|
||||||
'feature.selection.filter_list': string[]
|
'feature.selection.filter_list': string[]
|
||||||
// redux/selectionStore/filterMode
|
// redux/selectionStore/filterMode
|
||||||
'feature.selection.filter_mode': string
|
'feature.selection.filter_mode': SelectionFilterMode
|
||||||
// redux/selectionStore/isFollowToolbar
|
// redux/selectionStore/isFollowToolbar
|
||||||
'feature.selection.follow_toolbar': boolean
|
'feature.selection.follow_toolbar': boolean
|
||||||
// redux/selectionStore/isRemeberWinSize
|
// redux/selectionStore/isRemeberWinSize
|
||||||
'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': SelectionTriggerMode
|
||||||
// redux/shortcuts/shortcuts.clear_topic
|
// redux/shortcuts/shortcuts.clear_topic
|
||||||
'shortcut.chat.clear': Record<string, unknown>
|
'shortcut.chat.clear': Record<string, unknown>
|
||||||
// redux/shortcuts/shortcuts.copy_last_message
|
// redux/shortcuts/shortcuts.copy_last_message
|
||||||
|
|||||||
18
packages/shared/data/types.d.ts
vendored
18
packages/shared/data/types.d.ts
vendored
@ -3,6 +3,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 interface PreferenceUpdateOptions {
|
||||||
|
optimistic: boolean
|
||||||
|
}
|
||||||
|
|
||||||
export type PreferenceShortcutType = {
|
export type PreferenceShortcutType = {
|
||||||
key: string[]
|
key: string[]
|
||||||
editable: boolean
|
editable: boolean
|
||||||
@ -10,6 +14,16 @@ export type PreferenceShortcutType = {
|
|||||||
system: boolean
|
system: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface PreferenceUpdateOptions {
|
export type SelectionTriggerMode = 'selected' | 'ctrlkey' | 'shortcut'
|
||||||
optimistic: boolean
|
export type SelectionFilterMode = 'default' | 'whitelist' | 'blacklist'
|
||||||
|
export interface SelectionActionItem {
|
||||||
|
id: string
|
||||||
|
name: string
|
||||||
|
enabled: boolean
|
||||||
|
isBuiltIn: boolean
|
||||||
|
icon?: string
|
||||||
|
prompt?: string
|
||||||
|
assistantId?: string
|
||||||
|
selectedText?: string
|
||||||
|
searchEngine?: string
|
||||||
}
|
}
|
||||||
|
|||||||
@ -159,16 +159,16 @@ if (!app.requestSingleInstanceLock()) {
|
|||||||
|
|
||||||
await preferenceService.initialize()
|
await preferenceService.initialize()
|
||||||
|
|
||||||
// Create two test windows for cross-window preference sync testing
|
// // Create two test windows for cross-window preference sync testing
|
||||||
logger.info('Creating test windows for PreferenceService cross-window sync testing')
|
// logger.info('Creating test windows for PreferenceService cross-window sync testing')
|
||||||
const testWindow1 = dataRefactorMigrateService.createTestWindow()
|
// const testWindow1 = dataRefactorMigrateService.createTestWindow()
|
||||||
const testWindow2 = dataRefactorMigrateService.createTestWindow()
|
// const testWindow2 = dataRefactorMigrateService.createTestWindow()
|
||||||
|
|
||||||
// Position windows to avoid overlap
|
// // Position windows to avoid overlap
|
||||||
testWindow1.once('ready-to-show', () => {
|
// testWindow1.once('ready-to-show', () => {
|
||||||
const [x, y] = testWindow1.getPosition()
|
// const [x, y] = testWindow1.getPosition()
|
||||||
testWindow2.setPosition(x + 50, y + 50)
|
// testWindow2.setPosition(x + 50, y + 50)
|
||||||
})
|
// })
|
||||||
/************FOR TESTING ONLY END****************/
|
/************FOR TESTING ONLY END****************/
|
||||||
|
|
||||||
// Set app user model id for windows
|
// Set app user model id for windows
|
||||||
|
|||||||
@ -2,6 +2,7 @@ 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'
|
||||||
|
import type { SelectionActionItem, SelectionFilterMode, SelectionTriggerMode } from '@shared/data/types'
|
||||||
import { IpcChannel } from '@shared/IpcChannel'
|
import { IpcChannel } from '@shared/IpcChannel'
|
||||||
import { app, BrowserWindow, ipcMain, screen, systemPreferences } from 'electron'
|
import { app, BrowserWindow, ipcMain, screen, systemPreferences } from 'electron'
|
||||||
import { join } from 'path'
|
import { join } from 'path'
|
||||||
@ -13,8 +14,6 @@ import type {
|
|||||||
TextSelectionData
|
TextSelectionData
|
||||||
} from 'selection-hook'
|
} from 'selection-hook'
|
||||||
|
|
||||||
import type { ActionItem } from '../../renderer/src/types/selectionTypes'
|
|
||||||
|
|
||||||
const logger = loggerService.withContext('SelectionService')
|
const logger = loggerService.withContext('SelectionService')
|
||||||
|
|
||||||
const isSupportedOS = isWin || isMac
|
const isSupportedOS = isWin || isMac
|
||||||
@ -1248,7 +1247,7 @@ export class SelectionService {
|
|||||||
* @param actionItem Action item to process
|
* @param actionItem Action item to process
|
||||||
* @param isFullScreen [macOS] only macOS has the available isFullscreen mode
|
* @param isFullScreen [macOS] only macOS has the available isFullscreen mode
|
||||||
*/
|
*/
|
||||||
public processAction(actionItem: ActionItem, isFullScreen: boolean = false): void {
|
public processAction(actionItem: SelectionActionItem, isFullScreen: boolean = false): void {
|
||||||
const actionWindow = this.popActionWindow()
|
const actionWindow = this.popActionWindow()
|
||||||
|
|
||||||
actionWindow.webContents.send(IpcChannel.Selection_UpdateActionData, actionItem)
|
actionWindow.webContents.send(IpcChannel.Selection_UpdateActionData, actionItem)
|
||||||
@ -1476,7 +1475,7 @@ export class SelectionService {
|
|||||||
preferenceService.set('feature.selection.enabled', enabled)
|
preferenceService.set('feature.selection.enabled', enabled)
|
||||||
})
|
})
|
||||||
|
|
||||||
ipcMain.handle(IpcChannel.Selection_SetTriggerMode, (_, triggerMode: string) => {
|
ipcMain.handle(IpcChannel.Selection_SetTriggerMode, (_, triggerMode: SelectionTriggerMode) => {
|
||||||
preferenceService.set('feature.selection.trigger_mode', triggerMode)
|
preferenceService.set('feature.selection.trigger_mode', triggerMode)
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -1488,7 +1487,7 @@ export class SelectionService {
|
|||||||
preferenceService.set('feature.selection.remember_win_size', isRemeberWinSize)
|
preferenceService.set('feature.selection.remember_win_size', isRemeberWinSize)
|
||||||
})
|
})
|
||||||
|
|
||||||
ipcMain.handle(IpcChannel.Selection_SetFilterMode, (_, filterMode: string) => {
|
ipcMain.handle(IpcChannel.Selection_SetFilterMode, (_, filterMode: SelectionFilterMode) => {
|
||||||
preferenceService.set('feature.selection.filter_mode', filterMode)
|
preferenceService.set('feature.selection.filter_mode', filterMode)
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -1497,9 +1496,12 @@ export class SelectionService {
|
|||||||
})
|
})
|
||||||
|
|
||||||
// [macOS] only macOS has the available isFullscreen mode
|
// [macOS] only macOS has the available isFullscreen mode
|
||||||
ipcMain.handle(IpcChannel.Selection_ProcessAction, (_, actionItem: ActionItem, isFullScreen: boolean = false) => {
|
ipcMain.handle(
|
||||||
selectionService?.processAction(actionItem, isFullScreen)
|
IpcChannel.Selection_ProcessAction,
|
||||||
})
|
(_, actionItem: SelectionActionItem, isFullScreen: boolean = false) => {
|
||||||
|
selectionService?.processAction(actionItem, isFullScreen)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
ipcMain.handle(IpcChannel.Selection_ActionWindowClose, (event) => {
|
ipcMain.handle(IpcChannel.Selection_ActionWindowClose, (event) => {
|
||||||
const actionWindow = BrowserWindow.fromWebContents(event.sender)
|
const actionWindow = BrowserWindow.fromWebContents(event.sender)
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
import '@renderer/databases'
|
import '@renderer/databases'
|
||||||
|
|
||||||
|
import { preferenceService } from '@data/PreferenceService'
|
||||||
import { loggerService } from '@logger'
|
import { loggerService } from '@logger'
|
||||||
import store, { persistor } from '@renderer/store'
|
import store, { persistor } from '@renderer/store'
|
||||||
import { Provider } from 'react-redux'
|
import { Provider } from 'react-redux'
|
||||||
@ -15,6 +16,8 @@ import Router from './Router'
|
|||||||
|
|
||||||
const logger = loggerService.withContext('App.tsx')
|
const logger = loggerService.withContext('App.tsx')
|
||||||
|
|
||||||
|
preferenceService.loadAll()
|
||||||
|
|
||||||
function App(): React.ReactElement {
|
function App(): React.ReactElement {
|
||||||
logger.info('App initialized')
|
logger.info('App initialized')
|
||||||
|
|
||||||
|
|||||||
@ -109,7 +109,7 @@ export class PreferenceService {
|
|||||||
this.notifyChangeListeners(key)
|
this.notifyChangeListeners(key)
|
||||||
|
|
||||||
// Auto-subscribe to this key for future updates
|
// Auto-subscribe to this key for future updates
|
||||||
await this.subscribeToKeyInternal(key)
|
await this.subscribeToKeyInternal([key])
|
||||||
|
|
||||||
return value
|
return value
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@ -230,7 +230,7 @@ export class PreferenceService {
|
|||||||
|
|
||||||
this.notifyChangeListeners(key)
|
this.notifyChangeListeners(key)
|
||||||
|
|
||||||
await this.subscribeToKeyInternal(key as PreferenceKeyType)
|
await this.subscribeToKeyInternal([key as PreferenceKeyType])
|
||||||
}
|
}
|
||||||
|
|
||||||
return { ...cachedResults, ...uncachedResults }
|
return { ...cachedResults, ...uncachedResults }
|
||||||
@ -343,15 +343,16 @@ export class PreferenceService {
|
|||||||
/**
|
/**
|
||||||
* Subscribe to a specific key for change notifications
|
* Subscribe to a specific key for change notifications
|
||||||
*/
|
*/
|
||||||
private async subscribeToKeyInternal(key: PreferenceKeyType): Promise<void> {
|
private async subscribeToKeyInternal(keys: PreferenceKeyType[]): Promise<void> {
|
||||||
if (this.subscribedKeys.has(key)) return
|
const keysToSubscribe = keys.filter((key) => !this.subscribedKeys.has(key))
|
||||||
|
if (keysToSubscribe.length === 0) return
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await window.api.preference.subscribe([key])
|
await window.api.preference.subscribe(keysToSubscribe)
|
||||||
this.subscribedKeys.add(key)
|
keysToSubscribe.forEach((key) => this.subscribedKeys.add(key))
|
||||||
logger.debug(`Subscribed to preference key: ${key}`)
|
logger.debug(`Subscribed to preference keys: ${keysToSubscribe.join(', ')}`)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error(`Failed to subscribe to preference key ${key}:`, error as Error)
|
logger.error(`Failed to subscribe to preference keys ${keysToSubscribe.join(', ')}:`, error as Error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -379,7 +380,7 @@ export class PreferenceService {
|
|||||||
keyListeners.add(callback)
|
keyListeners.add(callback)
|
||||||
|
|
||||||
// Auto-subscribe to this key for updates
|
// Auto-subscribe to this key for updates
|
||||||
this.subscribeToKeyInternal(key)
|
this.subscribeToKeyInternal([key])
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
keyListeners.delete(callback)
|
keyListeners.delete(callback)
|
||||||
@ -417,11 +418,10 @@ export class PreferenceService {
|
|||||||
|
|
||||||
// Notify change listeners for the loaded value
|
// Notify change listeners for the loaded value
|
||||||
this.notifyChangeListeners(key)
|
this.notifyChangeListeners(key)
|
||||||
|
|
||||||
// Auto-subscribe to this key if not already subscribed
|
|
||||||
await this.subscribeToKeyInternal(key as PreferenceKeyType)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await this.subscribeToKeyInternal(Object.keys(allPreferences) as PreferenceKeyType[])
|
||||||
|
|
||||||
this.fullCacheLoaded = true
|
this.fullCacheLoaded = true
|
||||||
logger.info(`Loaded all ${Object.keys(allPreferences).length} preferences into cache`)
|
logger.info(`Loaded all ${Object.keys(allPreferences).length} preferences into cache`)
|
||||||
|
|
||||||
|
|||||||
@ -1,11 +1,9 @@
|
|||||||
import { usePreference } from '@data/hooks/usePreference'
|
import { usePreference } from '@data/hooks/usePreference'
|
||||||
import { loggerService } from '@logger'
|
|
||||||
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 { getSelectionDescriptionLabel } from '@renderer/i18n/label'
|
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 SelectionToolbar from '@renderer/windows/selection/toolbar/SelectionToolbar'
|
||||||
|
import type { SelectionFilterMode, SelectionTriggerMode } from '@shared/data/types'
|
||||||
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'
|
||||||
import { FC, useEffect, useState } from 'react'
|
import { FC, useEffect, useState } from 'react'
|
||||||
@ -26,35 +24,9 @@ import MacProcessTrustHintModal from './components/MacProcessTrustHintModal'
|
|||||||
import SelectionActionsList from './components/SelectionActionsList'
|
import SelectionActionsList from './components/SelectionActionsList'
|
||||||
import SelectionFilterListModal from './components/SelectionFilterListModal'
|
import SelectionFilterListModal from './components/SelectionFilterListModal'
|
||||||
|
|
||||||
const logger = loggerService.withContext('Settings:SelectionAssistant')
|
|
||||||
|
|
||||||
const SelectionAssistantSettings: FC = () => {
|
const SelectionAssistantSettings: FC = () => {
|
||||||
const { theme } = useTheme()
|
const { theme } = useTheme()
|
||||||
const { t } = useTranslation()
|
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, setSelectionEnabled] = usePreference('feature.selection.enabled')
|
const [selectionEnabled, setSelectionEnabled] = usePreference('feature.selection.enabled')
|
||||||
const [triggerMode, setTriggerMode] = usePreference('feature.selection.trigger_mode')
|
const [triggerMode, setTriggerMode] = usePreference('feature.selection.trigger_mode')
|
||||||
@ -68,18 +40,6 @@ const SelectionAssistantSettings: FC = () => {
|
|||||||
const [filterList, setFilterList] = usePreference('feature.selection.filter_list')
|
const [filterList, setFilterList] = usePreference('feature.selection.filter_list')
|
||||||
const [actionItems, setActionItems] = usePreference('feature.selection.action_items')
|
const [actionItems, setActionItems] = usePreference('feature.selection.action_items')
|
||||||
|
|
||||||
logger.debug(`selectionEnabled: ${selectionEnabled}`)
|
|
||||||
logger.debug(`triggerMode: ${triggerMode}`)
|
|
||||||
logger.debug(`isCompact: ${isCompact}`)
|
|
||||||
logger.debug(`isAutoClose: ${isAutoClose}`)
|
|
||||||
logger.debug(`isAutoPin: ${isAutoPin}`)
|
|
||||||
logger.debug(`isFollowToolbar: ${isFollowToolbar}`)
|
|
||||||
logger.debug(`isRemeberWinSize: ${isRemeberWinSize}`)
|
|
||||||
logger.debug(`actionWindowOpacity: ${actionWindowOpacity}`)
|
|
||||||
logger.debug(`filterMode: ${filterMode}`)
|
|
||||||
logger.debug(`filterList: ${filterList}`)
|
|
||||||
logger.debug(`actionItems: ${actionItems}`)
|
|
||||||
|
|
||||||
const isSupportedOS = isWin || isMac
|
const isSupportedOS = isWin || isMac
|
||||||
|
|
||||||
const [isFilterListModalOpen, setIsFilterListModalOpen] = useState(false)
|
const [isFilterListModalOpen, setIsFilterListModalOpen] = useState(false)
|
||||||
@ -168,7 +128,7 @@ const SelectionAssistantSettings: FC = () => {
|
|||||||
</SettingLabel>
|
</SettingLabel>
|
||||||
<Radio.Group
|
<Radio.Group
|
||||||
value={triggerMode}
|
value={triggerMode}
|
||||||
onChange={(e) => setTriggerMode(e.target.value as TriggerMode)}
|
onChange={(e) => setTriggerMode(e.target.value as SelectionTriggerMode)}
|
||||||
buttonStyle="solid">
|
buttonStyle="solid">
|
||||||
<Tooltip placement="top" title={t('selection.settings.toolbar.trigger_mode.selected_note')} arrow>
|
<Tooltip placement="top" title={t('selection.settings.toolbar.trigger_mode.selected_note')} arrow>
|
||||||
<Radio.Button value="selected">{t('selection.settings.toolbar.trigger_mode.selected')}</Radio.Button>
|
<Radio.Button value="selected">{t('selection.settings.toolbar.trigger_mode.selected')}</Radio.Button>
|
||||||
@ -257,7 +217,7 @@ const SelectionAssistantSettings: FC = () => {
|
|||||||
</SettingRow>
|
</SettingRow>
|
||||||
</SettingGroup>
|
</SettingGroup>
|
||||||
|
|
||||||
<SelectionActionsList actionItems={actionItems as ActionItem[]} setActionItems={setActionItems} />
|
<SelectionActionsList actionItems={actionItems} setActionItems={setActionItems} />
|
||||||
|
|
||||||
<SettingGroup theme={theme}>
|
<SettingGroup theme={theme}>
|
||||||
<SettingTitle>{t('selection.settings.advanced.title')}</SettingTitle>
|
<SettingTitle>{t('selection.settings.advanced.title')}</SettingTitle>
|
||||||
@ -269,7 +229,7 @@ const SelectionAssistantSettings: FC = () => {
|
|||||||
</SettingLabel>
|
</SettingLabel>
|
||||||
<Radio.Group
|
<Radio.Group
|
||||||
value={filterMode ?? 'default'}
|
value={filterMode ?? 'default'}
|
||||||
onChange={(e) => setFilterMode(e.target.value as FilterMode)}
|
onChange={(e) => setFilterMode(e.target.value as SelectionFilterMode)}
|
||||||
buttonStyle="solid">
|
buttonStyle="solid">
|
||||||
<Radio.Button value="default">{t('selection.settings.advanced.filter_mode.default')}</Radio.Button>
|
<Radio.Button value="default">{t('selection.settings.advanced.filter_mode.default')}</Radio.Button>
|
||||||
<Radio.Button value="whitelist">{t('selection.settings.advanced.filter_mode.whitelist')}</Radio.Button>
|
<Radio.Button value="whitelist">{t('selection.settings.advanced.filter_mode.whitelist')}</Radio.Button>
|
||||||
|
|||||||
27
src/renderer/src/types/selectionTypes.d.ts
vendored
27
src/renderer/src/types/selectionTypes.d.ts
vendored
@ -1,27 +0,0 @@
|
|||||||
export type TriggerMode = 'selected' | 'ctrlkey' | 'shortcut'
|
|
||||||
export type FilterMode = 'default' | 'whitelist' | 'blacklist'
|
|
||||||
export interface ActionItem {
|
|
||||||
id: string
|
|
||||||
name: string
|
|
||||||
enabled: boolean
|
|
||||||
isBuiltIn: boolean
|
|
||||||
icon?: string
|
|
||||||
prompt?: string
|
|
||||||
assistantId?: string
|
|
||||||
selectedText?: string
|
|
||||||
searchEngine?: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface SelectionState {
|
|
||||||
selectionEnabled: boolean
|
|
||||||
triggerMode: TriggerMode
|
|
||||||
isCompact: boolean
|
|
||||||
isAutoClose: boolean
|
|
||||||
isAutoPin: boolean
|
|
||||||
isFollowToolbar: boolean
|
|
||||||
isRemeberWinSize: boolean
|
|
||||||
filterMode: FilterMode
|
|
||||||
filterList: string[]
|
|
||||||
actionWindowOpacity: number
|
|
||||||
actionItems: ActionItem[]
|
|
||||||
}
|
|
||||||
@ -1,9 +1,9 @@
|
|||||||
|
import { usePreference } from '@data/hooks/usePreference'
|
||||||
import { isMac } from '@renderer/config/constant'
|
import { isMac } from '@renderer/config/constant'
|
||||||
import { useSelectionAssistant } from '@renderer/hooks/useSelectionAssistant'
|
|
||||||
import { useSettings } from '@renderer/hooks/useSettings'
|
import { useSettings } from '@renderer/hooks/useSettings'
|
||||||
import i18n from '@renderer/i18n'
|
import i18n from '@renderer/i18n'
|
||||||
import type { ActionItem } from '@renderer/types/selectionTypes'
|
|
||||||
import { defaultLanguage } from '@shared/config/constant'
|
import { defaultLanguage } from '@shared/config/constant'
|
||||||
|
import type { SelectionActionItem } from '@shared/data/types'
|
||||||
import { IpcChannel } from '@shared/IpcChannel'
|
import { IpcChannel } from '@shared/IpcChannel'
|
||||||
import { Button, Slider, Tooltip } from 'antd'
|
import { Button, Slider, Tooltip } from 'antd'
|
||||||
import { Droplet, Minus, Pin, X } from 'lucide-react'
|
import { Droplet, Minus, Pin, X } from 'lucide-react'
|
||||||
@ -20,10 +20,13 @@ const SelectionActionApp: FC = () => {
|
|||||||
|
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
|
||||||
const [action, setAction] = useState<ActionItem | null>(null)
|
const [action, setAction] = useState<SelectionActionItem | null>(null)
|
||||||
const isActionLoaded = useRef(false)
|
const isActionLoaded = useRef(false)
|
||||||
|
|
||||||
const { isAutoClose, isAutoPin, actionWindowOpacity } = useSelectionAssistant()
|
const [isAutoClose] = usePreference('feature.selection.auto_close')
|
||||||
|
const [isAutoPin] = usePreference('feature.selection.auto_pin')
|
||||||
|
const [actionWindowOpacity] = usePreference('feature.selection.action_window_opacity')
|
||||||
|
|
||||||
const [isPinned, setIsPinned] = useState(isAutoPin)
|
const [isPinned, setIsPinned] = useState(isAutoPin)
|
||||||
const [isWindowFocus, setIsWindowFocus] = useState(true)
|
const [isWindowFocus, setIsWindowFocus] = useState(true)
|
||||||
|
|
||||||
@ -38,7 +41,7 @@ const SelectionActionApp: FC = () => {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const actionListenRemover = window.electron?.ipcRenderer.on(
|
const actionListenRemover = window.electron?.ipcRenderer.on(
|
||||||
IpcChannel.Selection_UpdateActionData,
|
IpcChannel.Selection_UpdateActionData,
|
||||||
(_, actionItem: ActionItem) => {
|
(_, actionItem: SelectionActionItem) => {
|
||||||
setAction(actionItem)
|
setAction(actionItem)
|
||||||
isActionLoaded.current = true
|
isActionLoaded.current = true
|
||||||
}
|
}
|
||||||
|
|||||||
@ -11,8 +11,8 @@ import {
|
|||||||
} from '@renderer/services/AssistantService'
|
} from '@renderer/services/AssistantService'
|
||||||
import { pauseTrace } from '@renderer/services/SpanManagerService'
|
import { pauseTrace } from '@renderer/services/SpanManagerService'
|
||||||
import { Assistant, Topic } from '@renderer/types'
|
import { Assistant, Topic } from '@renderer/types'
|
||||||
import type { ActionItem } from '@renderer/types/selectionTypes'
|
|
||||||
import { abortCompletion } from '@renderer/utils/abortController'
|
import { abortCompletion } from '@renderer/utils/abortController'
|
||||||
|
import type { SelectionActionItem } from '@shared/data/types'
|
||||||
import { ChevronDown } from 'lucide-react'
|
import { ChevronDown } from 'lucide-react'
|
||||||
import React, { FC, useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
import React, { FC, useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
@ -22,7 +22,7 @@ import { processMessages } from './ActionUtils'
|
|||||||
import WindowFooter from './WindowFooter'
|
import WindowFooter from './WindowFooter'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
action: ActionItem
|
action: SelectionActionItem
|
||||||
scrollToBottom?: () => void
|
scrollToBottom?: () => void
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,12 +1,12 @@
|
|||||||
import '@renderer/assets/styles/selection-toolbar.scss'
|
import '@renderer/assets/styles/selection-toolbar.scss'
|
||||||
|
|
||||||
|
import { usePreference } from '@data/hooks/usePreference'
|
||||||
import { loggerService } from '@logger'
|
import { loggerService } from '@logger'
|
||||||
import { AppLogo } from '@renderer/config/env'
|
import { AppLogo } from '@renderer/config/env'
|
||||||
import { useSelectionAssistant } from '@renderer/hooks/useSelectionAssistant'
|
|
||||||
import { useSettings } from '@renderer/hooks/useSettings'
|
import { useSettings } from '@renderer/hooks/useSettings'
|
||||||
import i18n from '@renderer/i18n'
|
import i18n from '@renderer/i18n'
|
||||||
import type { ActionItem } from '@renderer/types/selectionTypes'
|
|
||||||
import { defaultLanguage } from '@shared/config/constant'
|
import { defaultLanguage } from '@shared/config/constant'
|
||||||
|
import type { SelectionActionItem } from '@shared/data/types'
|
||||||
import { IpcChannel } from '@shared/IpcChannel'
|
import { IpcChannel } from '@shared/IpcChannel'
|
||||||
import { Avatar } from 'antd'
|
import { Avatar } from 'antd'
|
||||||
import { ClipboardCheck, ClipboardCopy, ClipboardX, MessageSquareHeart } from 'lucide-react'
|
import { ClipboardCheck, ClipboardCopy, ClipboardX, MessageSquareHeart } from 'lucide-react'
|
||||||
@ -32,9 +32,9 @@ const updateWindowSize = () => {
|
|||||||
* ActionIcons is a component that renders the action icons
|
* ActionIcons is a component that renders the action icons
|
||||||
*/
|
*/
|
||||||
const ActionIcons: FC<{
|
const ActionIcons: FC<{
|
||||||
actionItems: ActionItem[]
|
actionItems: SelectionActionItem[]
|
||||||
isCompact: boolean
|
isCompact: boolean
|
||||||
handleAction: (action: ActionItem) => void
|
handleAction: (action: SelectionActionItem) => void
|
||||||
copyIconStatus: 'normal' | 'success' | 'fail'
|
copyIconStatus: 'normal' | 'success' | 'fail'
|
||||||
copyIconAnimation: 'none' | 'enter' | 'exit'
|
copyIconAnimation: 'none' | 'enter' | 'exit'
|
||||||
}> = memo(({ actionItems, isCompact, handleAction, copyIconStatus, copyIconAnimation }) => {
|
}> = memo(({ actionItems, isCompact, handleAction, copyIconStatus, copyIconAnimation }) => {
|
||||||
@ -67,7 +67,7 @@ const ActionIcons: FC<{
|
|||||||
}, [copyIconStatus, copyIconAnimation])
|
}, [copyIconStatus, copyIconAnimation])
|
||||||
|
|
||||||
const renderActionButton = useCallback(
|
const renderActionButton = useCallback(
|
||||||
(action: ActionItem) => {
|
(action: SelectionActionItem) => {
|
||||||
const displayName = action.isBuiltIn ? t(action.name) : action.name
|
const displayName = action.isBuiltIn ? t(action.name) : action.name
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -99,7 +99,8 @@ const ActionIcons: FC<{
|
|||||||
*/
|
*/
|
||||||
const SelectionToolbar: FC<{ demo?: boolean }> = ({ demo = false }) => {
|
const SelectionToolbar: FC<{ demo?: boolean }> = ({ demo = false }) => {
|
||||||
const { language, customCss } = useSettings()
|
const { language, customCss } = useSettings()
|
||||||
const { isCompact, actionItems } = useSelectionAssistant()
|
const [isCompact] = usePreference('feature.selection.compact')
|
||||||
|
const [actionItems] = usePreference('feature.selection.action_items')
|
||||||
const [animateKey, setAnimateKey] = useState(0)
|
const [animateKey, setAnimateKey] = useState(0)
|
||||||
const [copyIconStatus, setCopyIconStatus] = useState<'normal' | 'success' | 'fail'>('normal')
|
const [copyIconStatus, setCopyIconStatus] = useState<'normal' | 'success' | 'fail'>('normal')
|
||||||
const [copyIconAnimation, setCopyIconAnimation] = useState<'none' | 'enter' | 'exit'>('none')
|
const [copyIconAnimation, setCopyIconAnimation] = useState<'none' | 'enter' | 'exit'>('none')
|
||||||
@ -179,7 +180,7 @@ const SelectionToolbar: FC<{ demo?: boolean }> = ({ demo = false }) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const handleAction = useCallback(
|
const handleAction = useCallback(
|
||||||
(action: ActionItem) => {
|
(action: SelectionActionItem) => {
|
||||||
if (demo) return
|
if (demo) return
|
||||||
|
|
||||||
/** avoid mutating the original action, it will cause syncing issue */
|
/** avoid mutating the original action, it will cause syncing issue */
|
||||||
@ -216,7 +217,7 @@ const SelectionToolbar: FC<{ demo?: boolean }> = ({ demo = false }) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleSearch = (action: ActionItem) => {
|
const handleSearch = (action: SelectionActionItem) => {
|
||||||
if (!action.searchEngine) return
|
if (!action.searchEngine) return
|
||||||
|
|
||||||
const customUrl = action.searchEngine.split('|')[1]
|
const customUrl = action.searchEngine.split('|')[1]
|
||||||
@ -230,14 +231,14 @@ const SelectionToolbar: FC<{ demo?: boolean }> = ({ demo = false }) => {
|
|||||||
/**
|
/**
|
||||||
* Quote the selected text to the inputbar of the main window
|
* Quote the selected text to the inputbar of the main window
|
||||||
*/
|
*/
|
||||||
const handleQuote = (action: ActionItem) => {
|
const handleQuote = (action: SelectionActionItem) => {
|
||||||
if (action.selectedText) {
|
if (action.selectedText) {
|
||||||
window.api?.quoteToMainWindow(action.selectedText)
|
window.api?.quoteToMainWindow(action.selectedText)
|
||||||
window.api?.selection.hideToolbar()
|
window.api?.selection.hideToolbar()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleDefaultAction = (action: ActionItem) => {
|
const handleDefaultAction = (action: SelectionActionItem) => {
|
||||||
// [macOS] only macOS has the available isFullscreen mode
|
// [macOS] only macOS has the available isFullscreen mode
|
||||||
window.api?.selection.processAction(action, isFullScreen.current)
|
window.api?.selection.processAction(action, isFullScreen.current)
|
||||||
window.api?.selection.hideToolbar()
|
window.api?.selection.hideToolbar()
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user