mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2026-01-06 21:35:52 +08:00
feat(ContextMenu): add spell check and dictionary suggestions to context menu (#7067)
* feat(ContextMenu): add spell check and dictionary suggestions to context menu - Implemented spell check functionality in the context menu with options to learn spelling and view dictionary suggestions. - Updated WindowService to enable spellcheck in the webview. - Enabled spell check in Inputbar and MessageEditor components. * feat(SpellCheck): implement spell check language settings and initialization - Added support for configuring spell check languages based on user-selected language. - Introduced IPC channel for setting spell check languages. - Updated settings to manage spell check enablement and languages. - Enhanced UI to allow users to toggle spell check functionality and select languages. - Default spell check languages are set based on the current UI language if none are specified. * refactor(SpellCheck): enhance spell check language mapping and UI settings - Updated spell check language mapping to default to English for unsupported languages. - Improved UI logic to only update spell check languages when enabled and no manual selections are made. - Added a new selection component for users to choose from commonly supported spell check languages. * feat(SpellCheck): integrate spell check functionality into Inputbar and MessageEditor - Added enableSpellCheck setting to control spell check functionality in both Inputbar and MessageEditor components. - Updated spellCheck prop to utilize the new setting, enhancing user experience by allowing customization of spell check behavior. * refactor(SpellCheck): move spell check initialization to WindowService - Removed spell check language initialization from index.ts and integrated it into WindowService. - Added setupSpellCheck method to configure spell check languages based on user settings. - Enhanced error handling for spell check language setup. * feat(SpellCheck): add enable spell check functionality and IPC channel - Introduced a new IPC channel for enabling/disabling spell check functionality. - Updated the preload API to include a method for setting spell check enablement. - Modified the main IPC handler to manage spell check settings based on user input. - Simplified spell check language handling in the settings component by directly invoking the new API method. * refactor(SpellCheck): remove spellcheck option from WindowService configuration - Removed the spellcheck property from the WindowService configuration object. - This change streamlines the configuration setup as spell check functionality is now managed through IPC channels. * feat(i18n): add spell check translations for Japanese, Russian, and Traditional Chinese - Added new translations for spell check functionality in ja-jp, ru-ru, and zh-tw locale files. - Included descriptions and language selection options for spell check settings to enhance user experience. * feat(migrate): add spell check configuration migration - Implemented migration for spell check settings, disabling spell check and clearing selected languages in the new configuration. - Enhanced error handling to ensure state consistency during migration process. * fix(migrate): ensure spell check settings are updated safely - Added a check to ensure state.settings exists before modifying spell check settings during migration. - Removed redundant error handling that returned the state unmodified in case of an error. * fix(WindowService): set default values for spell check configuration and update related UI texts * refactor(Inputbar, MessageEditor): remove contextMenu attribute and add context menu handling in MessageEditor --------- Co-authored-by: beyondkmp <beyondkmkp@gmail.com>
This commit is contained in:
parent
be15206234
commit
bbe380cc9e
@ -3,6 +3,8 @@ export enum IpcChannel {
|
|||||||
App_ClearCache = 'app:clear-cache',
|
App_ClearCache = 'app:clear-cache',
|
||||||
App_SetLaunchOnBoot = 'app:set-launch-on-boot',
|
App_SetLaunchOnBoot = 'app:set-launch-on-boot',
|
||||||
App_SetLanguage = 'app:set-language',
|
App_SetLanguage = 'app:set-language',
|
||||||
|
App_SetEnableSpellCheck = 'app:set-enable-spell-check',
|
||||||
|
App_SetSpellCheckLanguages = 'app:set-spell-check-languages',
|
||||||
App_ShowUpdateDialog = 'app:show-update-dialog',
|
App_ShowUpdateDialog = 'app:show-update-dialog',
|
||||||
App_CheckForUpdate = 'app:check-for-update',
|
App_CheckForUpdate = 'app:check-for-update',
|
||||||
App_Reload = 'app:reload',
|
App_Reload = 'app:reload',
|
||||||
|
|||||||
@ -87,6 +87,26 @@ export function registerIpc(mainWindow: BrowserWindow, app: Electron.App) {
|
|||||||
configManager.setLanguage(language)
|
configManager.setLanguage(language)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// spell check
|
||||||
|
ipcMain.handle(IpcChannel.App_SetEnableSpellCheck, (_, isEnable: boolean) => {
|
||||||
|
const windows = BrowserWindow.getAllWindows()
|
||||||
|
windows.forEach((window) => {
|
||||||
|
window.webContents.session.setSpellCheckerEnabled(isEnable)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
// spell check languages
|
||||||
|
ipcMain.handle(IpcChannel.App_SetSpellCheckLanguages, (_, languages: string[]) => {
|
||||||
|
if (languages.length === 0) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const windows = BrowserWindow.getAllWindows()
|
||||||
|
windows.forEach((window) => {
|
||||||
|
window.webContents.session.setSpellCheckerLanguages(languages)
|
||||||
|
})
|
||||||
|
configManager.set('spellCheckLanguages', languages)
|
||||||
|
})
|
||||||
|
|
||||||
// launch on boot
|
// launch on boot
|
||||||
ipcMain.handle(IpcChannel.App_SetLaunchOnBoot, (_, openAtLogin: boolean) => {
|
ipcMain.handle(IpcChannel.App_SetLaunchOnBoot, (_, openAtLogin: boolean) => {
|
||||||
// Set login item settings for windows and mac
|
// Set login item settings for windows and mac
|
||||||
|
|||||||
@ -9,7 +9,18 @@ class ContextMenu {
|
|||||||
const template: MenuItemConstructorOptions[] = this.createEditMenuItems(properties)
|
const template: MenuItemConstructorOptions[] = this.createEditMenuItems(properties)
|
||||||
const filtered = template.filter((item) => item.visible !== false)
|
const filtered = template.filter((item) => item.visible !== false)
|
||||||
if (filtered.length > 0) {
|
if (filtered.length > 0) {
|
||||||
const menu = Menu.buildFromTemplate([...filtered, ...this.createInspectMenuItems(w)])
|
let template = [...filtered, ...this.createInspectMenuItems(w)]
|
||||||
|
const dictionarySuggestions = this.createDictionarySuggestions(properties, w)
|
||||||
|
if (dictionarySuggestions.length > 0) {
|
||||||
|
template = [
|
||||||
|
...dictionarySuggestions,
|
||||||
|
{ type: 'separator' },
|
||||||
|
this.createSpellCheckMenuItem(properties, w),
|
||||||
|
{ type: 'separator' },
|
||||||
|
...template
|
||||||
|
]
|
||||||
|
}
|
||||||
|
const menu = Menu.buildFromTemplate(template)
|
||||||
menu.popup()
|
menu.popup()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@ -72,6 +83,53 @@ class ContextMenu {
|
|||||||
|
|
||||||
return template
|
return template
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private createSpellCheckMenuItem(
|
||||||
|
properties: Electron.ContextMenuParams,
|
||||||
|
mainWindow: Electron.BrowserWindow
|
||||||
|
): MenuItemConstructorOptions {
|
||||||
|
const hasText = properties.selectionText.length > 0
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: 'learnSpelling',
|
||||||
|
label: '&Learn Spelling',
|
||||||
|
visible: Boolean(properties.isEditable && hasText && properties.misspelledWord),
|
||||||
|
click: () => {
|
||||||
|
mainWindow.webContents.session.addWordToSpellCheckerDictionary(properties.misspelledWord)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private createDictionarySuggestions(
|
||||||
|
properties: Electron.ContextMenuParams,
|
||||||
|
mainWindow: Electron.BrowserWindow
|
||||||
|
): MenuItemConstructorOptions[] {
|
||||||
|
const hasText = properties.selectionText.length > 0
|
||||||
|
|
||||||
|
if (!hasText || !properties.misspelledWord) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
|
if (properties.dictionarySuggestions.length === 0) {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
id: 'dictionarySuggestions',
|
||||||
|
label: 'No Guesses Found',
|
||||||
|
visible: true,
|
||||||
|
enabled: false
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
return properties.dictionarySuggestions.map((suggestion) => ({
|
||||||
|
id: 'dictionarySuggestions',
|
||||||
|
label: suggestion,
|
||||||
|
visible: Boolean(properties.isEditable && hasText && properties.misspelledWord),
|
||||||
|
click: (menuItem: Electron.MenuItem) => {
|
||||||
|
mainWindow.webContents.replaceMisspelling(menuItem.label)
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const contextMenu = new ContextMenu()
|
export const contextMenu = new ContextMenu()
|
||||||
|
|||||||
@ -95,6 +95,7 @@ export class WindowService {
|
|||||||
|
|
||||||
this.setupMaximize(mainWindow, mainWindowState.isMaximized)
|
this.setupMaximize(mainWindow, mainWindowState.isMaximized)
|
||||||
this.setupContextMenu(mainWindow)
|
this.setupContextMenu(mainWindow)
|
||||||
|
this.setupSpellCheck(mainWindow)
|
||||||
this.setupWindowEvents(mainWindow)
|
this.setupWindowEvents(mainWindow)
|
||||||
this.setupWebContentsHandlers(mainWindow)
|
this.setupWebContentsHandlers(mainWindow)
|
||||||
this.setupWindowLifecycleEvents(mainWindow)
|
this.setupWindowLifecycleEvents(mainWindow)
|
||||||
@ -102,6 +103,18 @@ export class WindowService {
|
|||||||
this.loadMainWindowContent(mainWindow)
|
this.loadMainWindowContent(mainWindow)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private setupSpellCheck(mainWindow: BrowserWindow) {
|
||||||
|
const enableSpellCheck = configManager.get('enableSpellCheck', false)
|
||||||
|
if (enableSpellCheck) {
|
||||||
|
try {
|
||||||
|
const spellCheckLanguages = configManager.get('spellCheckLanguages', []) as string[]
|
||||||
|
spellCheckLanguages.length > 0 && mainWindow.webContents.session.setSpellCheckerLanguages(spellCheckLanguages)
|
||||||
|
} catch (error) {
|
||||||
|
Logger.error('Failed to set spell check languages:', error as Error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private setupMainWindowMonitor(mainWindow: BrowserWindow) {
|
private setupMainWindowMonitor(mainWindow: BrowserWindow) {
|
||||||
mainWindow.webContents.on('render-process-gone', (_, details) => {
|
mainWindow.webContents.on('render-process-gone', (_, details) => {
|
||||||
Logger.error(`Renderer process crashed with: ${JSON.stringify(details)}`)
|
Logger.error(`Renderer process crashed with: ${JSON.stringify(details)}`)
|
||||||
|
|||||||
@ -17,6 +17,8 @@ const api = {
|
|||||||
checkForUpdate: () => ipcRenderer.invoke(IpcChannel.App_CheckForUpdate),
|
checkForUpdate: () => ipcRenderer.invoke(IpcChannel.App_CheckForUpdate),
|
||||||
showUpdateDialog: () => ipcRenderer.invoke(IpcChannel.App_ShowUpdateDialog),
|
showUpdateDialog: () => ipcRenderer.invoke(IpcChannel.App_ShowUpdateDialog),
|
||||||
setLanguage: (lang: string) => ipcRenderer.invoke(IpcChannel.App_SetLanguage, lang),
|
setLanguage: (lang: string) => ipcRenderer.invoke(IpcChannel.App_SetLanguage, lang),
|
||||||
|
setEnableSpellCheck: (isEnable: boolean) => ipcRenderer.invoke(IpcChannel.App_SetEnableSpellCheck, isEnable),
|
||||||
|
setSpellCheckLanguages: (languages: string[]) => ipcRenderer.invoke(IpcChannel.App_SetSpellCheckLanguages, languages),
|
||||||
setLaunchOnBoot: (isActive: boolean) => ipcRenderer.invoke(IpcChannel.App_SetLaunchOnBoot, isActive),
|
setLaunchOnBoot: (isActive: boolean) => ipcRenderer.invoke(IpcChannel.App_SetLaunchOnBoot, isActive),
|
||||||
setLaunchToTray: (isActive: boolean) => ipcRenderer.invoke(IpcChannel.App_SetLaunchToTray, isActive),
|
setLaunchToTray: (isActive: boolean) => ipcRenderer.invoke(IpcChannel.App_SetLaunchToTray, isActive),
|
||||||
setTray: (isActive: boolean) => ipcRenderer.invoke(IpcChannel.App_SetTray, isActive),
|
setTray: (isActive: boolean) => ipcRenderer.invoke(IpcChannel.App_SetTray, isActive),
|
||||||
|
|||||||
@ -864,7 +864,7 @@
|
|||||||
"paint_course": "tutorial",
|
"paint_course": "tutorial",
|
||||||
"prompt_placeholder_edit": "Enter your image description, text drawing uses \"double quotes\" to wrap",
|
"prompt_placeholder_edit": "Enter your image description, text drawing uses \"double quotes\" to wrap",
|
||||||
"prompt_placeholder_en": "Enter your image description, currently Imagen only supports English prompts",
|
"prompt_placeholder_en": "Enter your image description, currently Imagen only supports English prompts",
|
||||||
"proxy_required": "Open the proxy and enable “TUN mode” to view generated images or copy them to the browser for opening. In the future, domestic direct connection will be supported",
|
"proxy_required": "Open the proxy and enable \"TUN mode\" to view generated images or copy them to the browser for opening. In the future, domestic direct connection will be supported",
|
||||||
"image_file_required": "Please upload an image first",
|
"image_file_required": "Please upload an image first",
|
||||||
"image_file_retry": "Please re-upload an image first",
|
"image_file_retry": "Please re-upload an image first",
|
||||||
"image_placeholder": "No image available",
|
"image_placeholder": "No image available",
|
||||||
@ -1392,6 +1392,8 @@
|
|||||||
"general.user_name": "User Name",
|
"general.user_name": "User Name",
|
||||||
"general.user_name.placeholder": "Enter your name",
|
"general.user_name.placeholder": "Enter your name",
|
||||||
"general.view_webdav_settings": "View WebDAV settings",
|
"general.view_webdav_settings": "View WebDAV settings",
|
||||||
|
"general.spell_check": "Spell Check",
|
||||||
|
"general.spell_check.languages": "Use spell check for",
|
||||||
"input.auto_translate_with_space": "Quickly translate with 3 spaces",
|
"input.auto_translate_with_space": "Quickly translate with 3 spaces",
|
||||||
"input.show_translate_confirm": "Show translation confirmation dialog",
|
"input.show_translate_confirm": "Show translation confirmation dialog",
|
||||||
"input.target_language": "Target language",
|
"input.target_language": "Target language",
|
||||||
|
|||||||
@ -1387,6 +1387,8 @@
|
|||||||
"general.user_name": "ユーザー名",
|
"general.user_name": "ユーザー名",
|
||||||
"general.user_name.placeholder": "ユーザー名を入力",
|
"general.user_name.placeholder": "ユーザー名を入力",
|
||||||
"general.view_webdav_settings": "WebDAV設定を表示",
|
"general.view_webdav_settings": "WebDAV設定を表示",
|
||||||
|
"general.spell_check": "スペルチェック",
|
||||||
|
"general.spell_check.languages": "スペルチェック言語",
|
||||||
"input.auto_translate_with_space": "スペースを3回押して翻訳",
|
"input.auto_translate_with_space": "スペースを3回押して翻訳",
|
||||||
"input.target_language": "目標言語",
|
"input.target_language": "目標言語",
|
||||||
"input.target_language.chinese": "簡体字中国語",
|
"input.target_language.chinese": "簡体字中国語",
|
||||||
|
|||||||
@ -1387,6 +1387,8 @@
|
|||||||
"general.user_name": "Имя пользователя",
|
"general.user_name": "Имя пользователя",
|
||||||
"general.user_name.placeholder": "Введите ваше имя",
|
"general.user_name.placeholder": "Введите ваше имя",
|
||||||
"general.view_webdav_settings": "Просмотр настроек WebDAV",
|
"general.view_webdav_settings": "Просмотр настроек WebDAV",
|
||||||
|
"general.spell_check": "Проверка орфографии",
|
||||||
|
"general.spell_check.languages": "Языки проверки орфографии",
|
||||||
"input.auto_translate_with_space": "Быстрый перевод с помощью 3-х пробелов",
|
"input.auto_translate_with_space": "Быстрый перевод с помощью 3-х пробелов",
|
||||||
"input.target_language": "Целевой язык",
|
"input.target_language": "Целевой язык",
|
||||||
"input.target_language.chinese": "Китайский упрощенный",
|
"input.target_language.chinese": "Китайский упрощенный",
|
||||||
|
|||||||
@ -863,8 +863,8 @@
|
|||||||
"learn_more": "了解更多",
|
"learn_more": "了解更多",
|
||||||
"paint_course": "教程",
|
"paint_course": "教程",
|
||||||
"prompt_placeholder_edit": "输入你的图片描述,文本绘制用 \"双引号\" 包裹",
|
"prompt_placeholder_edit": "输入你的图片描述,文本绘制用 \"双引号\" 包裹",
|
||||||
"prompt_placeholder_en": "输入”英文“图片描述,目前 Imagen 仅支持英文提示词",
|
"prompt_placeholder_en": "输入\"英文\"图片描述,目前 Imagen 仅支持英文提示词",
|
||||||
"proxy_required": "打开代理并开启”TUN模式“查看生成图片或复制到浏览器打开,后续会支持国内直连",
|
"proxy_required": "打开代理并开启\"TUN模式\"查看生成图片或复制到浏览器打开,后续会支持国内直连",
|
||||||
"image_file_required": "请先上传图片",
|
"image_file_required": "请先上传图片",
|
||||||
"image_file_retry": "请重新上传图片",
|
"image_file_retry": "请重新上传图片",
|
||||||
"image_placeholder": "暂无图片",
|
"image_placeholder": "暂无图片",
|
||||||
@ -960,7 +960,7 @@
|
|||||||
"magic_prompt_option_tip": "智能优化放大提示词"
|
"magic_prompt_option_tip": "智能优化放大提示词"
|
||||||
},
|
},
|
||||||
"text_desc_required": "请先输入图片描述",
|
"text_desc_required": "请先输入图片描述",
|
||||||
"req_error_text": "运行失败,请重试。提示词避免“版权词”和”敏感词”哦。",
|
"req_error_text": "运行失败,请重试。提示词避免\"版权词\"和\"敏感词\"哦。",
|
||||||
"req_error_token": "请检查令牌有效性",
|
"req_error_token": "请检查令牌有效性",
|
||||||
"req_error_no_balance": "请检查令牌有效性",
|
"req_error_no_balance": "请检查令牌有效性",
|
||||||
"image_handle_required": "请先上传图片",
|
"image_handle_required": "请先上传图片",
|
||||||
@ -1390,9 +1390,11 @@
|
|||||||
"general.restore.button": "恢复",
|
"general.restore.button": "恢复",
|
||||||
"general.title": "常规设置",
|
"general.title": "常规设置",
|
||||||
"general.user_name": "用户名",
|
"general.user_name": "用户名",
|
||||||
"general.user_name.placeholder": "请输入用户名",
|
"general.user_name.placeholder": "输入您的姓名",
|
||||||
"general.view_webdav_settings": "查看 WebDAV 设置",
|
"general.view_webdav_settings": "查看 WebDAV 设置",
|
||||||
"input.auto_translate_with_space": "快速敲击3次空格翻译",
|
"general.spell_check": "拼写检查",
|
||||||
|
"general.spell_check.languages": "拼写检查语言",
|
||||||
|
"input.auto_translate_with_space": "3个空格快速翻译",
|
||||||
"input.show_translate_confirm": "显示翻译确认对话框",
|
"input.show_translate_confirm": "显示翻译确认对话框",
|
||||||
"input.target_language": "目标语言",
|
"input.target_language": "目标语言",
|
||||||
"input.target_language.chinese": "简体中文",
|
"input.target_language.chinese": "简体中文",
|
||||||
|
|||||||
@ -1389,6 +1389,8 @@
|
|||||||
"general.user_name": "使用者名稱",
|
"general.user_name": "使用者名稱",
|
||||||
"general.user_name.placeholder": "輸入您的名稱",
|
"general.user_name.placeholder": "輸入您的名稱",
|
||||||
"general.view_webdav_settings": "檢視 WebDAV 設定",
|
"general.view_webdav_settings": "檢視 WebDAV 設定",
|
||||||
|
"general.spell_check": "拼寫檢查",
|
||||||
|
"general.spell_check.languages": "拼寫檢查語言",
|
||||||
"input.auto_translate_with_space": "快速敲擊 3 次空格翻譯",
|
"input.auto_translate_with_space": "快速敲擊 3 次空格翻譯",
|
||||||
"input.show_translate_confirm": "顯示翻譯確認對話框",
|
"input.show_translate_confirm": "顯示翻譯確認對話框",
|
||||||
"input.target_language": "目標語言",
|
"input.target_language": "目標語言",
|
||||||
|
|||||||
@ -77,7 +77,8 @@ const Inputbar: FC<Props> = ({ assistant: _assistant, setActiveTopic, topic }) =
|
|||||||
showInputEstimatedTokens,
|
showInputEstimatedTokens,
|
||||||
autoTranslateWithSpace,
|
autoTranslateWithSpace,
|
||||||
enableQuickPanelTriggers,
|
enableQuickPanelTriggers,
|
||||||
enableBackspaceDeleteModel
|
enableBackspaceDeleteModel,
|
||||||
|
enableSpellCheck
|
||||||
} = useSettings()
|
} = useSettings()
|
||||||
const [expended, setExpend] = useState(false)
|
const [expended, setExpend] = useState(false)
|
||||||
const [estimateTokenCount, setEstimateTokenCount] = useState(0)
|
const [estimateTokenCount, setEstimateTokenCount] = useState(0)
|
||||||
@ -780,9 +781,8 @@ const Inputbar: FC<Props> = ({ assistant: _assistant, setActiveTopic, topic }) =
|
|||||||
: t('chat.input.placeholder', { key: getSendMessageShortcutLabel(sendMessageShortcut) })
|
: t('chat.input.placeholder', { key: getSendMessageShortcutLabel(sendMessageShortcut) })
|
||||||
}
|
}
|
||||||
autoFocus
|
autoFocus
|
||||||
contextMenu="true"
|
|
||||||
variant="borderless"
|
variant="borderless"
|
||||||
spellCheck={false}
|
spellCheck={enableSpellCheck}
|
||||||
rows={2}
|
rows={2}
|
||||||
ref={textareaRef}
|
ref={textareaRef}
|
||||||
style={{
|
style={{
|
||||||
|
|||||||
@ -40,7 +40,7 @@ const MessageBlockEditor: FC<Props> = ({ message, onSave, onResend, onCancel })
|
|||||||
const model = assistant.model || assistant.defaultModel
|
const model = assistant.model || assistant.defaultModel
|
||||||
const isVision = useMemo(() => isVisionModel(model), [model])
|
const isVision = useMemo(() => isVisionModel(model), [model])
|
||||||
const supportExts = useMemo(() => [...textExts, ...documentExts, ...(isVision ? imageExts : [])], [isVision])
|
const supportExts = useMemo(() => [...textExts, ...documentExts, ...(isVision ? imageExts : [])], [isVision])
|
||||||
const { pasteLongTextAsFile, pasteLongTextThreshold, fontSize, sendMessageShortcut } = useSettings()
|
const { pasteLongTextAsFile, pasteLongTextThreshold, fontSize, sendMessageShortcut, enableSpellCheck } = useSettings()
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const textareaRef = useRef<TextAreaRef>(null)
|
const textareaRef = useRef<TextAreaRef>(null)
|
||||||
const attachmentButtonRef = useRef<AttachmentButtonRef>(null)
|
const attachmentButtonRef = useRef<AttachmentButtonRef>(null)
|
||||||
@ -222,13 +222,16 @@ const MessageBlockEditor: FC<Props> = ({ message, onSave, onResend, onCancel })
|
|||||||
}}
|
}}
|
||||||
onKeyDown={(e) => handleKeyDown(e, block.id)}
|
onKeyDown={(e) => handleKeyDown(e, block.id)}
|
||||||
autoFocus
|
autoFocus
|
||||||
contextMenu="true"
|
spellCheck={enableSpellCheck}
|
||||||
spellCheck={false}
|
|
||||||
onPaste={(e) => onPaste(e.nativeEvent)}
|
onPaste={(e) => onPaste(e.nativeEvent)}
|
||||||
onFocus={() => {
|
onFocus={() => {
|
||||||
// 记录当前聚焦的组件
|
// 记录当前聚焦的组件
|
||||||
PasteService.setLastFocusedComponent('messageEditor')
|
PasteService.setLastFocusedComponent('messageEditor')
|
||||||
}}
|
}}
|
||||||
|
onContextMenu={(e) => {
|
||||||
|
// 阻止事件冒泡,避免触发全局的 Electron contextMenu
|
||||||
|
e.stopPropagation()
|
||||||
|
}}
|
||||||
style={{
|
style={{
|
||||||
fontSize,
|
fontSize,
|
||||||
padding: '0px 15px 8px 15px'
|
padding: '0px 15px 8px 15px'
|
||||||
|
|||||||
@ -2,8 +2,15 @@ import { useTheme } from '@renderer/context/ThemeProvider'
|
|||||||
import { useSettings } from '@renderer/hooks/useSettings'
|
import { useSettings } from '@renderer/hooks/useSettings'
|
||||||
import i18n from '@renderer/i18n'
|
import i18n from '@renderer/i18n'
|
||||||
import { RootState, useAppDispatch } from '@renderer/store'
|
import { RootState, useAppDispatch } from '@renderer/store'
|
||||||
import { setEnableDataCollection, setLanguage, setNotificationSettings } from '@renderer/store/settings'
|
import {
|
||||||
import { setProxyMode, setProxyUrl as _setProxyUrl } from '@renderer/store/settings'
|
setEnableDataCollection,
|
||||||
|
setEnableSpellCheck,
|
||||||
|
setLanguage,
|
||||||
|
setNotificationSettings,
|
||||||
|
setProxyMode,
|
||||||
|
setProxyUrl as _setProxyUrl,
|
||||||
|
setSpellCheckLanguages
|
||||||
|
} from '@renderer/store/settings'
|
||||||
import { LanguageVarious } from '@renderer/types'
|
import { LanguageVarious } from '@renderer/types'
|
||||||
import { NotificationSource } from '@renderer/types/notification'
|
import { NotificationSource } from '@renderer/types/notification'
|
||||||
import { isValidProxyUrl } from '@renderer/utils'
|
import { isValidProxyUrl } from '@renderer/utils'
|
||||||
@ -26,7 +33,8 @@ const GeneralSettings: FC = () => {
|
|||||||
trayOnClose,
|
trayOnClose,
|
||||||
tray,
|
tray,
|
||||||
proxyMode: storeProxyMode,
|
proxyMode: storeProxyMode,
|
||||||
enableDataCollection
|
enableDataCollection,
|
||||||
|
enableSpellCheck
|
||||||
} = useSettings()
|
} = useSettings()
|
||||||
const [proxyUrl, setProxyUrl] = useState<string | undefined>(storeProxyUrl)
|
const [proxyUrl, setProxyUrl] = useState<string | undefined>(storeProxyUrl)
|
||||||
const { theme } = useTheme()
|
const { theme } = useTheme()
|
||||||
@ -69,6 +77,11 @@ const GeneralSettings: FC = () => {
|
|||||||
i18n.changeLanguage(value)
|
i18n.changeLanguage(value)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleSpellCheckChange = (checked: boolean) => {
|
||||||
|
dispatch(setEnableSpellCheck(checked))
|
||||||
|
window.api.setEnableSpellCheck(checked)
|
||||||
|
}
|
||||||
|
|
||||||
const onSetProxyUrl = () => {
|
const onSetProxyUrl = () => {
|
||||||
if (proxyUrl && !isValidProxyUrl(proxyUrl)) {
|
if (proxyUrl && !isValidProxyUrl(proxyUrl)) {
|
||||||
window.message.error({ content: t('message.error.invalid.proxy.url'), key: 'proxy-error' })
|
window.message.error({ content: t('message.error.invalid.proxy.url'), key: 'proxy-error' })
|
||||||
@ -109,11 +122,30 @@ const GeneralSettings: FC = () => {
|
|||||||
]
|
]
|
||||||
|
|
||||||
const notificationSettings = useSelector((state: RootState) => state.settings.notification)
|
const notificationSettings = useSelector((state: RootState) => state.settings.notification)
|
||||||
|
const spellCheckLanguages = useSelector((state: RootState) => state.settings.spellCheckLanguages)
|
||||||
|
|
||||||
const handleNotificationChange = (type: NotificationSource, value: boolean) => {
|
const handleNotificationChange = (type: NotificationSource, value: boolean) => {
|
||||||
dispatch(setNotificationSettings({ ...notificationSettings, [type]: value }))
|
dispatch(setNotificationSettings({ ...notificationSettings, [type]: value }))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Define available spell check languages with display names (only commonly supported languages)
|
||||||
|
const spellCheckLanguageOptions = [
|
||||||
|
{ value: 'en-US', label: 'English (US)', flag: '🇺🇸' },
|
||||||
|
{ value: 'es', label: 'Español', flag: '🇪🇸' },
|
||||||
|
{ value: 'fr', label: 'Français', flag: '🇫🇷' },
|
||||||
|
{ value: 'de', label: 'Deutsch', flag: '🇩🇪' },
|
||||||
|
{ value: 'it', label: 'Italiano', flag: '🇮🇹' },
|
||||||
|
{ value: 'pt', label: 'Português', flag: '🇵🇹' },
|
||||||
|
{ value: 'ru', label: 'Русский', flag: '🇷🇺' },
|
||||||
|
{ value: 'nl', label: 'Nederlands', flag: '🇳🇱' },
|
||||||
|
{ value: 'pl', label: 'Polski', flag: '🇵🇱' }
|
||||||
|
]
|
||||||
|
|
||||||
|
const handleSpellCheckLanguagesChange = (selectedLanguages: string[]) => {
|
||||||
|
dispatch(setSpellCheckLanguages(selectedLanguages))
|
||||||
|
window.api.setSpellCheckLanguages(selectedLanguages)
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SettingContainer theme={theme}>
|
<SettingContainer theme={theme}>
|
||||||
<SettingGroup theme={theme}>
|
<SettingGroup theme={theme}>
|
||||||
@ -135,6 +167,37 @@ const GeneralSettings: FC = () => {
|
|||||||
</Select>
|
</Select>
|
||||||
</SettingRow>
|
</SettingRow>
|
||||||
<SettingDivider />
|
<SettingDivider />
|
||||||
|
<SettingRow>
|
||||||
|
<SettingRowTitle>{t('settings.general.spell_check')}</SettingRowTitle>
|
||||||
|
<Switch checked={enableSpellCheck} onChange={handleSpellCheckChange} />
|
||||||
|
</SettingRow>
|
||||||
|
{enableSpellCheck && (
|
||||||
|
<>
|
||||||
|
<SettingDivider />
|
||||||
|
<SettingRow>
|
||||||
|
<SettingRowTitle>{t('settings.general.spell_check.languages')}</SettingRowTitle>
|
||||||
|
<Select
|
||||||
|
mode="multiple"
|
||||||
|
value={spellCheckLanguages}
|
||||||
|
style={{ width: 280 }}
|
||||||
|
placeholder={t('settings.general.spell_check.languages')}
|
||||||
|
onChange={handleSpellCheckLanguagesChange}
|
||||||
|
options={spellCheckLanguageOptions.map((lang) => ({
|
||||||
|
value: lang.value,
|
||||||
|
label: (
|
||||||
|
<Space.Compact direction="horizontal" block>
|
||||||
|
<Space.Compact block>{lang.label}</Space.Compact>
|
||||||
|
<span role="img" aria-label={lang.flag}>
|
||||||
|
{lang.flag}
|
||||||
|
</span>
|
||||||
|
</Space.Compact>
|
||||||
|
)
|
||||||
|
}))}
|
||||||
|
/>
|
||||||
|
</SettingRow>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
<SettingDivider />
|
||||||
<SettingRow>
|
<SettingRow>
|
||||||
<SettingRowTitle>{t('settings.proxy.mode.title')}</SettingRowTitle>
|
<SettingRowTitle>{t('settings.proxy.mode.title')}</SettingRowTitle>
|
||||||
<Select
|
<Select
|
||||||
|
|||||||
@ -1603,6 +1603,10 @@ const migrateConfig = {
|
|||||||
state.settings.exportMenuOptions.plain_text = true
|
state.settings.exportMenuOptions.plain_text = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (state.settings) {
|
||||||
|
state.settings.enableSpellCheck = false
|
||||||
|
state.settings.spellCheckLanguages = []
|
||||||
|
}
|
||||||
return state
|
return state
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return state
|
return state
|
||||||
|
|||||||
@ -155,6 +155,8 @@ export interface SettingsState {
|
|||||||
minappsOpenLinkExternal: boolean
|
minappsOpenLinkExternal: boolean
|
||||||
// 隐私设置
|
// 隐私设置
|
||||||
enableDataCollection: boolean
|
enableDataCollection: boolean
|
||||||
|
enableSpellCheck: boolean
|
||||||
|
spellCheckLanguages: string[]
|
||||||
enableQuickPanelTriggers: boolean
|
enableQuickPanelTriggers: boolean
|
||||||
enableBackspaceDeleteModel: boolean
|
enableBackspaceDeleteModel: boolean
|
||||||
exportMenuOptions: {
|
exportMenuOptions: {
|
||||||
@ -298,6 +300,8 @@ export const initialState: SettingsState = {
|
|||||||
showOpenedMinappsInSidebar: true,
|
showOpenedMinappsInSidebar: true,
|
||||||
minappsOpenLinkExternal: false,
|
minappsOpenLinkExternal: false,
|
||||||
enableDataCollection: false,
|
enableDataCollection: false,
|
||||||
|
enableSpellCheck: false,
|
||||||
|
spellCheckLanguages: [],
|
||||||
enableQuickPanelTriggers: false,
|
enableQuickPanelTriggers: false,
|
||||||
enableBackspaceDeleteModel: true,
|
enableBackspaceDeleteModel: true,
|
||||||
exportMenuOptions: {
|
exportMenuOptions: {
|
||||||
@ -657,6 +661,12 @@ const settingsSlice = createSlice({
|
|||||||
setEnableDataCollection: (state, action: PayloadAction<boolean>) => {
|
setEnableDataCollection: (state, action: PayloadAction<boolean>) => {
|
||||||
state.enableDataCollection = action.payload
|
state.enableDataCollection = action.payload
|
||||||
},
|
},
|
||||||
|
setEnableSpellCheck: (state, action: PayloadAction<boolean>) => {
|
||||||
|
state.enableSpellCheck = action.payload
|
||||||
|
},
|
||||||
|
setSpellCheckLanguages: (state, action: PayloadAction<string[]>) => {
|
||||||
|
state.spellCheckLanguages = action.payload
|
||||||
|
},
|
||||||
setExportMenuOptions: (state, action: PayloadAction<typeof initialState.exportMenuOptions>) => {
|
setExportMenuOptions: (state, action: PayloadAction<typeof initialState.exportMenuOptions>) => {
|
||||||
state.exportMenuOptions = action.payload
|
state.exportMenuOptions = action.payload
|
||||||
},
|
},
|
||||||
@ -776,8 +786,10 @@ export const {
|
|||||||
setShowOpenedMinappsInSidebar,
|
setShowOpenedMinappsInSidebar,
|
||||||
setMinappsOpenLinkExternal,
|
setMinappsOpenLinkExternal,
|
||||||
setEnableDataCollection,
|
setEnableDataCollection,
|
||||||
setEnableQuickPanelTriggers,
|
setEnableSpellCheck,
|
||||||
|
setSpellCheckLanguages,
|
||||||
setExportMenuOptions,
|
setExportMenuOptions,
|
||||||
|
setEnableQuickPanelTriggers,
|
||||||
setEnableBackspaceDeleteModel,
|
setEnableBackspaceDeleteModel,
|
||||||
setOpenAISummaryText,
|
setOpenAISummaryText,
|
||||||
setOpenAIServiceTier,
|
setOpenAIServiceTier,
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user