refactor: shortcuts feature

This commit is contained in:
kangfenmao 2024-12-02 22:55:56 +08:00
parent cd3c053f81
commit 7f2f3ad88a
24 changed files with 276 additions and 303 deletions

View File

@ -7,8 +7,9 @@ export default defineConfig({
plugins: [externalizeDepsPlugin()], plugins: [externalizeDepsPlugin()],
resolve: { resolve: {
alias: { alias: {
'@main': resolve('src/main'),
'@types': resolve('src/renderer/src/types'), '@types': resolve('src/renderer/src/types'),
'@main': resolve('src/main') '@shared': resolve('packages/shared')
} }
} }
}, },
@ -16,14 +17,15 @@ export default defineConfig({
plugins: [externalizeDepsPlugin()] plugins: [externalizeDepsPlugin()]
}, },
renderer: { renderer: {
plugins: [react()],
resolve: { resolve: {
alias: { alias: {
'@renderer': resolve('src/renderer/src') '@renderer': resolve('src/renderer/src'),
'@shared': resolve('packages/shared')
} }
}, },
plugins: [react()],
optimizeDeps: { optimizeDeps: {
exclude: ['chunk-KNVOMWSO.js', 'chunk-2NJP6ETL.js'] exclude: []
} }
} }
}) })

View File

@ -0,0 +1,112 @@
export const imageExts = ['.jpg', '.jpeg', '.png', '.gif', '.bmp', '.webp']
export const videoExts = ['.mp4', '.avi', '.mov', '.wmv', '.flv', '.mkv']
export const audioExts = ['.mp3', '.wav', '.ogg', '.flac', '.aac']
export const documentExts = ['.pdf', '.docx', '.pptx', '.xlsx', '.odt', '.odp', '.ods']
export const textExts = [
'.txt', // 普通文本文件
'.md', // Markdown 文件
'.mdx', // Markdown 文件
'.html', // HTML 文件
'.htm', // HTML 文件的另一种扩展名
'.xml', // XML 文件
'.json', // JSON 文件
'.yaml', // YAML 文件
'.yml', // YAML 文件的另一种扩展名
'.csv', // 逗号分隔值文件
'.tsv', // 制表符分隔值文件
'.ini', // 配置文件
'.log', // 日志文件
'.rtf', // 富文本格式文件
'.tex', // LaTeX 文件
'.srt', // 字幕文件
'.xhtml', // XHTML 文件
'.nfo', // 信息文件(主要用于场景发布)
'.conf', // 配置文件
'.config', // 配置文件
'.env', // 环境变量文件
'.rst', // reStructuredText 文件
'.php', // PHP 脚本文件,包含嵌入的 HTML
'.js', // JavaScript 文件(部分是文本,部分可能包含代码)
'.ts', // TypeScript 文件
'.jsp', // JavaServer Pages 文件
'.aspx', // ASP.NET 文件
'.bat', // Windows 批处理文件
'.sh', // Unix/Linux Shell 脚本文件
'.py', // Python 脚本文件
'.rb', // Ruby 脚本文件
'.pl', // Perl 脚本文件
'.sql', // SQL 脚本文件
'.css', // Cascading Style Sheets 文件
'.less', // Less CSS 预处理器文件
'.scss', // Sass CSS 预处理器文件
'.sass', // Sass 文件
'.styl', // Stylus CSS 预处理器文件
'.coffee', // CoffeeScript 文件
'.ino', // Arduino 代码文件
'.asm', // Assembly 语言文件
'.go', // Go 语言文件
'.scala', // Scala 语言文件
'.swift', // Swift 语言文件
'.kt', // Kotlin 语言文件
'.rs', // Rust 语言文件
'.lua', // Lua 语言文件
'.groovy', // Groovy 语言文件
'.dart', // Dart 语言文件
'.hs', // Haskell 语言文件
'.clj', // Clojure 语言文件
'.cljs', // ClojureScript 语言文件
'.elm', // Elm 语言文件
'.erl', // Erlang 语言文件
'.ex', // Elixir 语言文件
'.exs', // Elixir 脚本文件
'.pug', // Pug (formerly Jade) 模板文件
'.haml', // Haml 模板文件
'.slim', // Slim 模板文件
'.tpl', // 模板文件(通用)
'.ejs', // Embedded JavaScript 模板文件
'.hbs', // Handlebars 模板文件
'.mustache', // Mustache 模板文件
'.jade', // Jade 模板文件 (已重命名为 Pug)
'.twig', // Twig 模板文件
'.blade', // Blade 模板文件 (Laravel)
'.vue', // Vue.js 单文件组件
'.jsx', // React JSX 文件
'.tsx', // React TSX 文件
'.graphql', // GraphQL 查询语言文件
'.gql', // GraphQL 查询语言文件
'.proto', // Protocol Buffers 文件
'.thrift', // Thrift 文件
'.toml', // TOML 配置文件
'.edn', // Clojure 数据表示文件
'.cake', // CakePHP 配置文件
'.ctp', // CakePHP 视图文件
'.cfm', // ColdFusion 标记语言文件
'.cfc', // ColdFusion 组件文件
'.m', // Objective-C 源文件
'.mm', // Objective-C++ 源文件
'.gradle', // Gradle 构建文件
'.groovy', // Gradle 构建文件
'.kts', // Kotlin Script 文件
'.java' // Java 代码文件
]
export const ZOOM_SHORTCUTS = [
{
key: 'zoom_in',
shortcut: ['CommandOrControl', '='],
editable: false,
enabled: true
},
{
key: 'zoom_out',
shortcut: ['CommandOrControl', '-'],
editable: false,
enabled: true
},
{
key: 'zoom_reset',
shortcut: ['CommandOrControl', '0'],
editable: false,
enabled: true
}
]

View File

@ -1,95 +1,3 @@
export const isMac = process.platform === 'darwin' export const isMac = process.platform === 'darwin'
export const isWin = process.platform === 'win32' export const isWin = process.platform === 'win32'
export const isLinux = process.platform === 'linux' export const isLinux = process.platform === 'linux'
export const imageExts = ['.jpg', '.jpeg', '.png', '.gif', '.bmp', '.webp']
export const videoExts = ['.mp4', '.avi', '.mov', '.wmv', '.flv', '.mkv']
export const audioExts = ['.mp3', '.wav', '.ogg', '.flac', '.aac']
export const documentExts = ['.pdf', '.docx', '.pptx', '.xlsx', '.odt', '.odp', '.ods']
export const textExts = [
'.txt', // 普通文本文件
'.md', // Markdown 文件
'.mdx', // Markdown 文件
'.html', // HTML 文件
'.htm', // HTML 文件的另一种扩展名
'.xml', // XML 文件
'.json', // JSON 文件
'.yaml', // YAML 文件
'.yml', // YAML 文件的另一种扩展名
'.csv', // 逗号分隔值文件
'.tsv', // 制表符分隔值文件
'.ini', // 配置文件
'.log', // 日志文件
'.rtf', // 富文本格式文件
'.tex', // LaTeX 文件
'.srt', // 字幕文件
'.xhtml', // XHTML 文件
'.nfo', // 信息文件(主要用于场景发布)
'.conf', // 配置文件
'.config', // 配置文件
'.env', // 环境变量文件
'.rst', // reStructuredText 文件
'.php', // PHP 脚本文件,包含嵌入的 HTML
'.js', // JavaScript 文件(部分是文本,部分可能包含代码)
'.ts', // TypeScript 文件
'.jsp', // JavaServer Pages 文件
'.aspx', // ASP.NET 文件
'.bat', // Windows 批处理文件
'.sh', // Unix/Linux Shell 脚本文件
'.py', // Python 脚本文件
'.rb', // Ruby 脚本文件
'.pl', // Perl 脚本文件
'.sql', // SQL 脚本文件
'.css', // Cascading Style Sheets 文件
'.less', // Less CSS 预处理器文件
'.scss', // Sass CSS 预处理器文件
'.sass', // Sass 文件
'.styl', // Stylus CSS 预处理器文件
'.coffee', // CoffeeScript 文件
'.ino', // Arduino 代码文件
'.asm', // Assembly 语言文件
'.go', // Go 语言文件
'.scala', // Scala 语言文件
'.swift', // Swift 语言文件
'.kt', // Kotlin 语言文件
'.rs', // Rust 语言文件
'.lua', // Lua 语言文件
'.groovy', // Groovy 语言文件
'.dart', // Dart 语言文件
'.hs', // Haskell 语言文件
'.clj', // Clojure 语言文件
'.cljs', // ClojureScript 语言文件
'.elm', // Elm 语言文件
'.erl', // Erlang 语言文件
'.ex', // Elixir 语言文件
'.exs', // Elixir 脚本文件
'.pug', // Pug (formerly Jade) 模板文件
'.haml', // Haml 模板文件
'.slim', // Slim 模板文件
'.tpl', // 模板文件(通用)
'.ejs', // Embedded JavaScript 模板文件
'.hbs', // Handlebars 模板文件
'.mustache', // Mustache 模板文件
'.jade', // Jade 模板文件 (已重命名为 Pug)
'.twig', // Twig 模板文件
'.blade', // Blade 模板文件 (Laravel)
'.vue', // Vue.js 单文件组件
'.jsx', // React JSX 文件
'.tsx', // React TSX 文件
'.graphql', // GraphQL 查询语言文件
'.gql', // GraphQL 查询语言文件
'.proto', // Protocol Buffers 文件
'.thrift', // Thrift 文件
'.toml', // TOML 配置文件
'.edn', // Clojure 数据表示文件
'.cake', // CakePHP 配置文件
'.ctp', // CakePHP 视图文件
'.cfm', // ColdFusion 标记语言文件
'.cfc', // ColdFusion 组件文件
'.m', // Objective-C 源文件
'.mm', // Objective-C++ 源文件
'.gradle', // Gradle 构建文件
'.groovy', // Gradle 构建文件
'.kts', // Kotlin Script 文件
'.java' // Java 代码文件
]

View File

@ -138,6 +138,7 @@ export function registerIpc(mainWindow: BrowserWindow, app: Electron.App) {
// shortcuts // shortcuts
ipcMain.handle('shortcuts:update', (_, shortcuts: Shortcut[]) => { ipcMain.handle('shortcuts:update', (_, shortcuts: Shortcut[]) => {
configManager.setShortcuts(shortcuts) configManager.setShortcuts(shortcuts)
log.info('[ipc] shortcuts updated', shortcuts)
// Refresh shortcuts registration // Refresh shortcuts registration
if (mainWindow) { if (mainWindow) {
unregisterAllShortcuts() unregisterAllShortcuts()

View File

@ -1,4 +1,5 @@
import { LanguageVarious, ThemeMode } from '@types' import { ZOOM_SHORTCUTS } from '@shared/config/constant'
import { LanguageVarious, Shortcut, ThemeMode } from '@types'
import { app } from 'electron' import { app } from 'electron'
import Store from 'electron-store' import Store from 'electron-store'
@ -72,7 +73,7 @@ export class ConfigManager {
} }
getShortcuts() { getShortcuts() {
return this.store.get('shortcuts') as Shortcut[] | undefined return this.store.get('shortcuts', ZOOM_SHORTCUTS) as Shortcut[] | []
} }
setShortcuts(shortcuts: Shortcut[]) { setShortcuts(shortcuts: Shortcut[]) {

View File

@ -1,5 +1,5 @@
import { documentExts, imageExts } from '@main/constant'
import { getFileType } from '@main/utils/file' import { getFileType } from '@main/utils/file'
import { documentExts, imageExts } from '@shared/config/constant'
import { FileType } from '@types' import { FileType } from '@types'
import * as crypto from 'crypto' import * as crypto from 'crypto'
import { import {

View File

@ -1,6 +1,5 @@
import { Shortcut } from '@types' import { Shortcut } from '@types'
import { BrowserWindow, globalShortcut } from 'electron' import { BrowserWindow, globalShortcut } from 'electron'
import Logger from 'electron-log'
import { configManager } from './ConfigManager' import { configManager } from './ConfigManager'
@ -9,9 +8,9 @@ let showAppAccelerator: string | null = null
function getShortcutHandler(shortcut: Shortcut) { function getShortcutHandler(shortcut: Shortcut) {
switch (shortcut.key) { switch (shortcut.key) {
case 'zoom_in': case 'zoom_in':
return () => handleZoom(0.1) return (window: BrowserWindow) => handleZoom(0.1)(window)
case 'zoom_out': case 'zoom_out':
return () => handleZoom(-0.1) return (window: BrowserWindow) => handleZoom(-0.1)(window)
case 'zoom_reset': case 'zoom_reset':
return (window: BrowserWindow) => { return (window: BrowserWindow) => {
window.webContents.setZoomFactor(1) window.webContents.setZoomFactor(1)
@ -46,7 +45,7 @@ function handleZoom(delta: number) {
} }
} }
function registerWindowShortcuts(window: BrowserWindow) { export function registerShortcuts(window: BrowserWindow) {
window.webContents.setZoomFactor(configManager.getZoomFactor()) window.webContents.setZoomFactor(configManager.getZoomFactor())
const register = () => { const register = () => {
@ -56,10 +55,15 @@ function registerWindowShortcuts(window: BrowserWindow) {
if (!shortcuts) return if (!shortcuts) return
shortcuts.forEach((shortcut) => { shortcuts.forEach((shortcut) => {
if (!shortcut.enabled || shortcut.shortcut.length === 0) return if (!shortcut.enabled || shortcut.shortcut.length === 0) {
return
}
const handler = getShortcutHandler(shortcut) const handler = getShortcutHandler(shortcut)
if (!handler) return
if (!handler) {
return
}
const accelerator = formatShortcutKey(shortcut.shortcut) const accelerator = formatShortcutKey(shortcut.shortcut)
@ -67,7 +71,22 @@ function registerWindowShortcuts(window: BrowserWindow) {
showAppAccelerator = accelerator showAppAccelerator = accelerator
} }
Logger.info(`Register shortcut: ${accelerator}`) if (shortcut.key.includes('zoom')) {
switch (shortcut.key) {
case 'zoom_in':
globalShortcut.register('CommandOrControl+=', () => handler(window))
globalShortcut.register('CommandOrControl+numadd', () => handler(window))
return
case 'zoom_out':
globalShortcut.register('CommandOrControl+-', () => handler(window))
globalShortcut.register('CommandOrControl+numsub', () => handler(window))
return
case 'zoom_reset':
globalShortcut.register('CommandOrControl+0', () => handler(window))
return
}
}
globalShortcut.register(accelerator, () => handler(window)) globalShortcut.register(accelerator, () => handler(window))
}) })
} }
@ -79,9 +98,7 @@ function registerWindowShortcuts(window: BrowserWindow) {
if (showAppAccelerator) { if (showAppAccelerator) {
const handler = getShortcutHandler({ key: 'show_app' } as Shortcut) const handler = getShortcutHandler({ key: 'show_app' } as Shortcut)
if (handler) { handler && globalShortcut.register(showAppAccelerator, () => handler(window))
globalShortcut.register(showAppAccelerator, () => handler(window))
}
} }
} }
@ -93,10 +110,6 @@ function registerWindowShortcuts(window: BrowserWindow) {
} }
} }
export function registerShortcuts(mainWindow: BrowserWindow) {
registerWindowShortcuts(mainWindow)
}
export function unregisterAllShortcuts() { export function unregisterAllShortcuts() {
showAppAccelerator = null showAppAccelerator = null
globalShortcut.unregisterAll() globalShortcut.unregisterAll()

View File

@ -1,6 +1,5 @@
import { audioExts, documentExts, imageExts, textExts, videoExts } from '@main/constant' import { audioExts, documentExts, imageExts, textExts, videoExts } from '@shared/config/constant'
import { FileTypes } from '@types'
import { FileTypes } from '../../renderer/src/types'
export function getFileType(ext: string): FileTypes { export function getFileType(ext: string): FileTypes {
ext = ext.toLowerCase() ext = ext.toLowerCase()

View File

@ -3,97 +3,8 @@ export const DEFAULT_CONTEXTCOUNT = 5
export const DEFAULT_MAX_TOKENS = 4096 export const DEFAULT_MAX_TOKENS = 4096
export const FONT_FAMILY = export const FONT_FAMILY =
"Ubuntu, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif" "Ubuntu, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif"
export const platform = window.electron?.process?.platform export const platform = window.electron?.process?.platform
export const isMac = platform === 'darwin' export const isMac = platform === 'darwin'
export const isWindows = platform === 'win32' || platform === 'win64' export const isWindows = platform === 'win32' || platform === 'win64'
export const isLinux = platform === 'linux' export const isLinux = platform === 'linux'
export const imageExts = ['.jpg', '.jpeg', '.png', '.gif', '.bmp', '.webp']
export const documentExts = ['.pdf', '.docx', '.pptx', '.xlsx', '.odt', '.odp', '.ods']
export const textExts = [
'.txt', // 普通文本文件
'.md', // Markdown 文件
'.mdx', // Markdown 文件
'.html', // HTML 文件
'.htm', // HTML 文件的另一种扩展名
'.xml', // XML 文件
'.json', // JSON 文件
'.yaml', // YAML 文件
'.yml', // YAML 文件的另一种扩展名
'.csv', // 逗号分隔值文件
'.tsv', // 制表符分隔值文件
'.ini', // 配置文件
'.log', // 日志文件
'.rtf', // 富文本格式文件
'.tex', // LaTeX 文件
'.srt', // 字幕文件
'.xhtml', // XHTML 文件
'.nfo', // 信息文件(主要用于场景发布)
'.conf', // 配置文件
'.config', // 配置文件
'.env', // 环境变量文件
'.rst', // reStructuredText 文件
'.php', // PHP 脚本文件,包含嵌入的 HTML
'.js', // JavaScript 文件(部分是文本,部分可能包含代码)
'.ts', // TypeScript 文件
'.jsp', // JavaServer Pages 文件
'.aspx', // ASP.NET 文件
'.bat', // Windows 批处理文件
'.sh', // Unix/Linux Shell 脚本文件
'.py', // Python 脚本文件
'.rb', // Ruby 脚本文件
'.pl', // Perl 脚本文件
'.sql', // SQL 脚本文件
'.css', // Cascading Style Sheets 文件
'.less', // Less CSS 预处理器文件
'.scss', // Sass CSS 预处理器文件
'.sass', // Sass 文件
'.styl', // Stylus CSS 预处理器文件
'.coffee', // CoffeeScript 文件
'.ino', // Arduino 代码文件
'.asm', // Assembly 语言文件
'.go', // Go 语言文件
'.scala', // Scala 语言文件
'.swift', // Swift 语言文件
'.kt', // Kotlin 语言文件
'.rs', // Rust 语言文件
'.lua', // Lua 语言文件
'.groovy', // Groovy 语言文件
'.dart', // Dart 语言文件
'.hs', // Haskell 语言文件
'.clj', // Clojure 语言文件
'.cljs', // ClojureScript 语言文件
'.elm', // Elm 语言文件
'.erl', // Erlang 语言文件
'.ex', // Elixir 语言文件
'.exs', // Elixir 脚本文件
'.pug', // Pug (formerly Jade) 模板文件
'.haml', // Haml 模板文件
'.slim', // Slim 模板文件
'.tpl', // 模板文件(通用)
'.ejs', // Embedded JavaScript 模板文件
'.hbs', // Handlebars 模板文件
'.mustache', // Mustache 模板文件
'.jade', // Jade 模板文件 (已重命名为 Pug)
'.twig', // Twig 模板文件
'.blade', // Blade 模板文件 (Laravel)
'.vue', // Vue.js 单文件组件
'.jsx', // React JSX 文件
'.tsx', // React TSX 文件
'.graphql', // GraphQL 查询语言文件
'.gql', // GraphQL 查询语言文件
'.proto', // Protocol Buffers 文件
'.thrift', // Thrift 文件
'.toml', // TOML 配置文件
'.edn', // Clojure 数据表示文件
'.cake', // CakePHP 配置文件
'.ctp', // CakePHP 视图文件
'.cfm', // ColdFusion 标记语言文件
'.cfc', // ColdFusion 组件文件
'.m', // Objective-C 源文件
'.mm', // Objective-C++ 源文件
'.gradle', // Gradle 构建文件
'.groovy', // Gradle 构建文件
'.kts', // Kotlin Script 文件
'.java' // Java 代码文件
]

View File

@ -1,9 +1,10 @@
import { isMac } from '@renderer/config/constant' import { isMac, isWindows } from '@renderer/config/constant'
import { isLocalAi } from '@renderer/config/env' import { isLocalAi } from '@renderer/config/env'
import db from '@renderer/databases' import db from '@renderer/databases'
import i18n from '@renderer/i18n' import i18n from '@renderer/i18n'
import { useAppDispatch } from '@renderer/store' import { useAppDispatch } from '@renderer/store'
import { setAvatar, setFilesPath } from '@renderer/store/runtime' import { setAvatar, setFilesPath } from '@renderer/store/runtime'
import { updateShortcut } from '@renderer/store/shortcuts'
import { runAsyncFunction } from '@renderer/utils' import { runAsyncFunction } from '@renderer/utils'
import { useLiveQuery } from 'dexie-react-hooks' import { useLiveQuery } from 'dexie-react-hooks'
import { useEffect } from 'react' import { useEffect } from 'react'
@ -11,6 +12,7 @@ import { useEffect } from 'react'
import { useDefaultModel } from './useAssistant' import { useDefaultModel } from './useAssistant'
import { useRuntime } from './useRuntime' import { useRuntime } from './useRuntime'
import { useSettings } from './useSettings' import { useSettings } from './useSettings'
import { useShortcuts } from './useShortcuts'
export function useAppInit() { export function useAppInit() {
const dispatch = useAppDispatch() const dispatch = useAppDispatch()
@ -18,6 +20,7 @@ export function useAppInit() {
const { minappShow } = useRuntime() const { minappShow } = useRuntime()
const { setDefaultModel, setTopicNamingModel, setTranslateModel } = useDefaultModel() const { setDefaultModel, setTopicNamingModel, setTranslateModel } = useDefaultModel()
const avatar = useLiveQuery(() => db.settings.get('image://avatar')) const avatar = useLiveQuery(() => db.settings.get('image://avatar'))
const { shortcuts } = useShortcuts()
useEffect(() => { useEffect(() => {
avatar?.value && dispatch(setAvatar(avatar.value)) avatar?.value && dispatch(setAvatar(avatar.value))
@ -69,4 +72,15 @@ export function useAppInit() {
dispatch(setFilesPath(info.filesPath)) dispatch(setFilesPath(info.filesPath))
}) })
}, [dispatch]) }, [dispatch])
useEffect(() => {
if (isWindows) {
shortcuts.forEach((shortcut) => {
if (shortcut.shortcut[0] === 'Command') {
shortcut.shortcut[0] = 'Ctrl'
dispatch(updateShortcut(shortcut))
}
})
}
}, [dispatch, shortcuts])
} }

View File

@ -49,7 +49,12 @@ export const useShortcut = (
}, },
{ {
enableOnFormTags: options.enableOnFormTags, enableOnFormTags: options.enableOnFormTags,
description: options.description || shortcutConfig?.name description: options.description || shortcutConfig?.key
} }
) )
} }
export function useShortcuts() {
const shortcuts = useAppSelector((state) => state.shortcuts.shortcuts)
return { shortcuts }
}

View File

@ -458,7 +458,9 @@
"reset_defaults": "Reset Defaults", "reset_defaults": "Reset Defaults",
"reset_defaults_confirm": "Are you sure you want to reset all shortcuts?", "reset_defaults_confirm": "Are you sure you want to reset all shortcuts?",
"press_shortcut": "Press Shortcut", "press_shortcut": "Press Shortcut",
"alt_warning": "Mac does not support the Alt key" "alt_warning": "Mac does not support the Alt key",
"reset_to_default": "Reset to Default",
"clear_shortcut": "Clear Shortcut"
}, },
"theme.auto": "Auto", "theme.auto": "Auto",
"theme.dark": "Dark", "theme.dark": "Dark",

View File

@ -458,7 +458,9 @@
"reset_defaults": "Сбросить настройки по умолчанию", "reset_defaults": "Сбросить настройки по умолчанию",
"reset_defaults_confirm": "Вы уверены, что хотите сбросить все горячие клавиши?", "reset_defaults_confirm": "Вы уверены, что хотите сбросить все горячие клавиши?",
"press_shortcut": "Нажмите сочетание клавиш", "press_shortcut": "Нажмите сочетание клавиш",
"alt_warning": "Mac не поддерживает Alt" "alt_warning": "Mac не поддерживает Alt",
"reset_to_default": "Сбросить настройки по умолчанию",
"clear_shortcut": "Очистить сочетание клавиш"
}, },
"theme.auto": "Автоматически", "theme.auto": "Автоматически",
"theme.dark": "Темная", "theme.dark": "Темная",

View File

@ -446,7 +446,9 @@
"reset_defaults": "重置默认快捷键", "reset_defaults": "重置默认快捷键",
"reset_defaults_confirm": "确定要重置所有快捷键吗?", "reset_defaults_confirm": "确定要重置所有快捷键吗?",
"press_shortcut": "按下快捷键", "press_shortcut": "按下快捷键",
"alt_warning": "Mac 系统不支持 Alt 键" "alt_warning": "Mac 系统不支持 Alt 键",
"reset_to_default": "重置为默认",
"clear_shortcut": "清除快捷键"
}, },
"theme.auto": "跟随系统", "theme.auto": "跟随系统",
"theme.dark": "深色主题", "theme.dark": "深色主题",

View File

@ -446,7 +446,9 @@
"reset_defaults": "重置預設快捷鍵", "reset_defaults": "重置預設快捷鍵",
"reset_defaults_confirm": "確定要重置所有快捷鍵嗎?", "reset_defaults_confirm": "確定要重置所有快捷鍵嗎?",
"press_shortcut": "按下快捷鍵", "press_shortcut": "按下快捷鍵",
"alt_warning": "Mac 系統不支持 Alt 鍵" "alt_warning": "Mac 系統不支持 Alt 鍵",
"reset_to_default": "重置為預設",
"clear_shortcut": "清除快捷鍵"
}, },
"theme.auto": "自動", "theme.auto": "自動",
"theme.dark": "深色主題", "theme.dark": "深色主題",

View File

@ -1,7 +1,7 @@
import { PaperClipOutlined } from '@ant-design/icons' import { PaperClipOutlined } from '@ant-design/icons'
import { documentExts, imageExts, textExts } from '@renderer/config/constant'
import { isVisionModel } from '@renderer/config/models' import { isVisionModel } from '@renderer/config/models'
import { FileType, Model } from '@renderer/types' import { FileType, Model } from '@renderer/types'
import { documentExts, imageExts, textExts } from '@shared/config/constant'
import { Tooltip } from 'antd' import { Tooltip } from 'antd'
import { FC } from 'react' import { FC } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'

View File

@ -9,7 +9,7 @@ import {
} from '@ant-design/icons' } from '@ant-design/icons'
import { PicCenterOutlined } from '@ant-design/icons' import { PicCenterOutlined } from '@ant-design/icons'
import TranslateButton from '@renderer/components/TranslateButton' import TranslateButton from '@renderer/components/TranslateButton'
import { documentExts, imageExts, isMac, textExts } from '@renderer/config/constant' import { isMac } from '@renderer/config/constant'
import { isVisionModel } from '@renderer/config/models' import { isVisionModel } from '@renderer/config/models'
import db from '@renderer/databases' import db from '@renderer/databases'
import { useAssistant } from '@renderer/hooks/useAssistant' import { useAssistant } from '@renderer/hooks/useAssistant'
@ -26,6 +26,7 @@ import store, { useAppDispatch, useAppSelector } from '@renderer/store'
import { setGenerating, setSearching } from '@renderer/store/runtime' import { setGenerating, setSearching } from '@renderer/store/runtime'
import { Assistant, FileType, Message, Topic } from '@renderer/types' import { Assistant, FileType, Message, Topic } from '@renderer/types'
import { delay, getFileExtension, uuid } from '@renderer/utils' import { delay, getFileExtension, uuid } from '@renderer/utils'
import { documentExts, imageExts, textExts } from '@shared/config/constant'
import { Button, Popconfirm, Tooltip } from 'antd' import { Button, Popconfirm, Tooltip } from 'antd'
import TextArea, { TextAreaRef } from 'antd/es/input/TextArea' import TextArea, { TextAreaRef } from 'antd/es/input/TextArea'
import dayjs from 'dayjs' import dayjs from 'dayjs'

View File

@ -11,7 +11,6 @@ import {
} from '@ant-design/icons' } from '@ant-design/icons'
import SelectModelPopup from '@renderer/components/Popups/SelectModelPopup' import SelectModelPopup from '@renderer/components/Popups/SelectModelPopup'
import TextEditPopup from '@renderer/components/Popups/TextEditPopup' import TextEditPopup from '@renderer/components/Popups/TextEditPopup'
import { useDefaultModel } from '@renderer/hooks/useAssistant'
import { EVENT_NAMES, EventEmitter } from '@renderer/services/EventService' import { EVENT_NAMES, EventEmitter } from '@renderer/services/EventService'
import { translateText } from '@renderer/services/TranslateService' import { translateText } from '@renderer/services/TranslateService'
import { Message, Model } from '@renderer/types' import { Message, Model } from '@renderer/types'
@ -37,7 +36,6 @@ const MessageMenubar: FC<Props> = (props) => {
const { message, index, model, isLastMessage, isAssistantMessage, setModel, onEditMessage, onDeleteMessage } = props const { message, index, model, isLastMessage, isAssistantMessage, setModel, onEditMessage, onDeleteMessage } = props
const { t } = useTranslation() const { t } = useTranslation()
const [copied, setCopied] = useState(false) const [copied, setCopied] = useState(false)
const { translateModel } = useDefaultModel()
const [isTranslating, setIsTranslating] = useState(false) const [isTranslating, setIsTranslating] = useState(false)
const isUserMessage = message.role === 'user' const isUserMessage = message.role === 'user'

View File

@ -490,8 +490,4 @@ const InfoIcon = styled(QuestionCircleOutlined)`
} }
` `
const RefreshIcon = styled(RedoOutlined)`
cursor: pointer;
`
export default PaintingsPage export default PaintingsPage

View File

@ -1,10 +1,11 @@
import { UndoOutlined } from '@ant-design/icons' import { ClearOutlined, UndoOutlined } from '@ant-design/icons'
import { HStack } from '@renderer/components/Layout' import { HStack } from '@renderer/components/Layout'
import { isMac } from '@renderer/config/constant' import { isMac } from '@renderer/config/constant'
import { useTheme } from '@renderer/context/ThemeProvider' import { useTheme } from '@renderer/context/ThemeProvider'
import { useAppDispatch, useAppSelector } from '@renderer/store' import { useAppDispatch, useAppSelector } from '@renderer/store'
import { initialState, resetShortcuts, updateShortcut } from '@renderer/store/shortcuts' import { initialState, resetShortcuts, toggleShortcut, updateShortcut } from '@renderer/store/shortcuts'
import { Button, Input, InputRef, Table as AntTable } from 'antd' import { Shortcut } from '@renderer/types'
import { Button, Input, InputRef, Switch, Table as AntTable, Tooltip } from 'antd'
import type { ColumnsType } from 'antd/es/table' import type { ColumnsType } from 'antd/es/table'
import { FC, useRef, useState } from 'react' import { FC, useRef, useState } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
@ -12,13 +13,6 @@ import styled from 'styled-components'
import { SettingContainer, SettingDivider, SettingGroup, SettingTitle } from '.' import { SettingContainer, SettingDivider, SettingGroup, SettingTitle } from '.'
interface ShortcutItem {
key: string
name: string
shortcut: string[]
enabled: boolean
}
const ShortcutSettings: FC = () => { const ShortcutSettings: FC = () => {
const { t } = useTranslation() const { t } = useTranslation()
const { theme } = useTheme() const { theme } = useTheme()
@ -27,7 +21,7 @@ const ShortcutSettings: FC = () => {
const inputRefs = useRef<Record<string, InputRef>>({}) const inputRefs = useRef<Record<string, InputRef>>({})
const [editingKey, setEditingKey] = useState<string | null>(null) const [editingKey, setEditingKey] = useState<string | null>(null)
const handleClear = (record: ShortcutItem) => { const handleClear = (record: Shortcut) => {
dispatch( dispatch(
updateShortcut({ updateShortcut({
...record, ...record,
@ -36,19 +30,19 @@ const ShortcutSettings: FC = () => {
) )
} }
const handleAddShortcut = (record: ShortcutItem) => { const handleAddShortcut = (record: Shortcut) => {
setEditingKey(record.key) setEditingKey(record.key)
setTimeout(() => { setTimeout(() => {
inputRefs.current[record.key]?.focus() inputRefs.current[record.key]?.focus()
}, 0) }, 0)
} }
const isShortcutModified = (record: ShortcutItem) => { const isShortcutModified = (record: Shortcut) => {
const defaultShortcut = initialState.shortcuts.find((s) => s.key === record.key) const defaultShortcut = initialState.shortcuts.find((s) => s.key === record.key)
return defaultShortcut?.shortcut.join('+') !== record.shortcut.join('+') return defaultShortcut?.shortcut.join('+') !== record.shortcut.join('+')
} }
const handleResetShortcut = (record: ShortcutItem) => { const handleResetShortcut = (record: Shortcut) => {
const defaultShortcut = initialState.shortcuts.find((s) => s.key === record.key) const defaultShortcut = initialState.shortcuts.find((s) => s.key === record.key)
if (defaultShortcut) { if (defaultShortcut) {
dispatch( dispatch(
@ -95,6 +89,8 @@ const ShortcutSettings: FC = () => {
return isMac ? '⌥' : 'Alt' return isMac ? '⌥' : 'Alt'
case 'Shift': case 'Shift':
return isMac ? '⇧' : 'Shift' return isMac ? '⇧' : 'Shift'
case 'CommandOrControl':
return isMac ? '⌘' : 'Ctrl'
case ' ': case ' ':
return 'Space' return 'Space'
default: default:
@ -104,7 +100,8 @@ const ShortcutSettings: FC = () => {
.join(' + ') .join(' + ')
} }
const handleKeyDown = (e: React.KeyboardEvent, record: ShortcutItem) => { const handleKeyDown = (e: React.KeyboardEvent, record: Shortcut) => {
console.debug('handleKeyDown', e, record)
e.preventDefault() e.preventDefault()
const keys: string[] = [] const keys: string[] = []
@ -115,6 +112,8 @@ const ShortcutSettings: FC = () => {
const key = e.key const key = e.key
console.debug('key', key)
if (!['Control', 'Alt', 'Shift', 'Meta'].includes(key)) { if (!['Control', 'Alt', 'Shift', 'Meta'].includes(key)) {
keys.push(key.toUpperCase()) keys.push(key.toUpperCase())
} }
@ -144,7 +143,7 @@ const ShortcutSettings: FC = () => {
}) })
} }
const columns: ColumnsType<ShortcutItem> = [ const columns: ColumnsType<Shortcut> = [
{ {
title: t('settings.shortcuts.action'), title: t('settings.shortcuts.action'),
dataIndex: 'name', dataIndex: 'name',
@ -155,14 +154,16 @@ const ShortcutSettings: FC = () => {
dataIndex: 'shortcut', dataIndex: 'shortcut',
key: 'shortcut', key: 'shortcut',
align: 'right', align: 'right',
render: (shortcut: string[], record: ShortcutItem) => { render: (shortcut: string[], record: Shortcut) => {
const isEditing = editingKey === record.key const isEditing = editingKey === record.key
const shortcutConfig = shortcuts.find((s) => s.key === record.key)
const isEditable = shortcutConfig?.editable !== false
return ( return (
<div style={{ display: 'flex', gap: '8px' }}> <HStack style={{ display: 'flex', gap: '8px', justifyContent: 'flex-end', alignItems: 'center' }}>
<div style={{ position: 'relative', flex: 1 }}> <HStack alignItems="center" style={{ position: 'relative' }}>
{isEditing ? ( {isEditing ? (
<Input <ShortcutInput
ref={(el) => el && (inputRefs.current[record.key] = el)} ref={(el) => el && (inputRefs.current[record.key] = el)}
value={formatShortcut(shortcut)} value={formatShortcut(shortcut)}
placeholder={t('settings.shortcuts.press_shortcut')} placeholder={t('settings.shortcuts.press_shortcut')}
@ -173,39 +174,51 @@ const ShortcutSettings: FC = () => {
setEditingKey(null) setEditingKey(null)
} }
}} }}
style={{ width: '120px' }}
suffix={
isShortcutModified(record) && (
<UndoOutlined
className="shortcut-undo-icon"
style={{
position: 'absolute',
right: '8px',
top: '50%',
transform: 'translateY(-50%)',
cursor: 'pointer',
color: '#999'
}}
onClick={() => {
handleResetShortcut(record)
setEditingKey(null)
}}
/>
)
}
/> />
) : ( ) : (
<div style={{ cursor: 'pointer', padding: '4px 11px' }} onClick={() => handleAddShortcut(record)}> <ShortcutText isEditable={isEditable} onClick={() => isEditable && handleAddShortcut(record)}>
{shortcut.length > 0 ? formatShortcut(shortcut) : t('settings.shortcuts.press_shortcut')} {shortcut.length > 0 ? formatShortcut(shortcut) : t('settings.shortcuts.press_shortcut')}
</div> </ShortcutText>
)} )}
</div> </HStack>
<Button onClick={() => (shortcut ? handleClear(record) : handleAddShortcut(record))}> </HStack>
{shortcut ? t('common.clear') : t('common.add')}
</Button>
</div>
) )
} }
},
{
title: t('settings.shortcuts.actions'),
key: 'actions',
align: 'right',
width: '70px',
render: (record: Shortcut) => (
<HStack style={{ display: 'flex', gap: '8px', justifyContent: 'flex-end', alignItems: 'center' }}>
<Tooltip title={t('settings.shortcuts.reset_to_default')}>
<Button
icon={<UndoOutlined />}
size="small"
onClick={() => handleResetShortcut(record)}
disabled={!isShortcutModified(record)}
/>
</Tooltip>
<Tooltip title={t('settings.shortcuts.clear_shortcut')}>
<Button
icon={<ClearOutlined />}
size="small"
onClick={() => handleClear(record)}
disabled={record.shortcut.length === 0 || !record.editable}
/>
</Tooltip>
</HStack>
)
},
{
title: t('settings.shortcuts.enabled'),
key: 'enabled',
align: 'right',
width: '50px',
render: (record: Shortcut) => (
<Switch size="small" checked={record.enabled} onChange={() => dispatch(toggleShortcut(record.key))} />
)
} }
] ]
@ -216,7 +229,7 @@ const ShortcutSettings: FC = () => {
<SettingDivider style={{ marginBottom: 0 }} /> <SettingDivider style={{ marginBottom: 0 }} />
<Table <Table
columns={columns as ColumnsType<unknown>} columns={columns as ColumnsType<unknown>}
dataSource={shortcuts.map((s) => ({ ...s, name: t(s.name) }))} dataSource={shortcuts.map((s) => ({ ...s, name: t(`settings.shortcuts.${s.key}`) }))}
pagination={false} pagination={false}
size="middle" size="middle"
showHeader={false} showHeader={false}
@ -245,4 +258,15 @@ const Table = styled(AntTable)`
} }
` `
const ShortcutInput = styled(Input)`
width: 120px;
text-align: center;
`
const ShortcutText = styled.span<{ isEditable: boolean }>`
cursor: ${({ isEditable }) => (isEditable ? 'pointer' : 'not-allowed')};
padding: 4px 11px;
opacity: ${({ isEditable }) => (isEditable ? 1 : 0.5)};
`
export default ShortcutSettings export default ShortcutSettings

View File

@ -1,6 +1,7 @@
import { createSlice, PayloadAction } from '@reduxjs/toolkit' import { createSlice, PayloadAction } from '@reduxjs/toolkit'
import { isMac } from '@renderer/config/constant' import { isMac } from '@renderer/config/constant'
import { Shortcut } from '@renderer/types' import { Shortcut } from '@renderer/types'
import { ZOOM_SHORTCUTS } from '@shared/config/constant'
export interface ShortcutsState { export interface ShortcutsState {
shortcuts: Shortcut[] shortcuts: Shortcut[]
@ -8,34 +9,17 @@ export interface ShortcutsState {
const initialState: ShortcutsState = { const initialState: ShortcutsState = {
shortcuts: [ shortcuts: [
...ZOOM_SHORTCUTS,
{ {
key: 'new_topic', key: 'new_topic',
name: 'settings.shortcuts.new_topic',
shortcut: [isMac ? 'Command' : 'Ctrl', 'N'], shortcut: [isMac ? 'Command' : 'Ctrl', 'N'],
enabled: true editable: true,
},
{
key: 'zoom_in',
name: 'settings.shortcuts.zoom_in',
shortcut: [isMac ? 'Command' : 'Ctrl', '='],
enabled: true
},
{
key: 'zoom_out',
name: 'settings.shortcuts.zoom_out',
shortcut: [isMac ? 'Command' : 'Ctrl', '-'],
enabled: true
},
{
key: 'zoom_reset',
name: 'settings.shortcuts.zoom_reset',
shortcut: [isMac ? 'Command' : 'Ctrl', '0'],
enabled: true enabled: true
}, },
{ {
key: 'show_app', key: 'show_app',
name: 'settings.shortcuts.show_app', shortcut: [],
shortcut: [isMac ? 'Command' : 'Ctrl', 'Shift', 'A'], editable: true,
enabled: true enabled: true
} }
] ]
@ -44,7 +28,6 @@ const initialState: ShortcutsState = {
const getSerializableShortcuts = (shortcuts: Shortcut[]) => { const getSerializableShortcuts = (shortcuts: Shortcut[]) => {
return shortcuts.map((shortcut) => ({ return shortcuts.map((shortcut) => ({
key: shortcut.key, key: shortcut.key,
name: shortcut.name,
shortcut: [...shortcut.shortcut], shortcut: [...shortcut.shortcut],
enabled: shortcut.enabled enabled: shortcut.enabled
})) }))

View File

@ -165,7 +165,7 @@ export type AppInfo = {
export interface Shortcut { export interface Shortcut {
key: string key: string
name: string
shortcut: string[] shortcut: string[]
editable: boolean
enabled: boolean enabled: boolean
} }

View File

@ -5,20 +5,19 @@
"src/main/**/*", "src/main/**/*",
"src/preload/**/*", "src/preload/**/*",
"src/main/env.d.ts", "src/main/env.d.ts",
"src/renderer/src/types/index.ts" "src/renderer/src/types/index.ts",
"packages/shared/**/*"
], ],
"compilerOptions": { "compilerOptions": {
"composite": true, "composite": true,
"types": [ "types": [
"electron-vite/node" "electron-vite/node"
], ],
"baseUrl": ".",
"paths": { "paths": {
"@types": [ "@main/*": ["src/main/*"],
"./src/renderer/src/types/index.ts" "@types": ["src/renderer/src/types/index.ts"],
], "@shared/*": ["packages/shared/*"]
"@main/*": [
"./src/main/*"
]
} }
} }
} }

View File

@ -1,20 +1,18 @@
{ {
"extends": "@electron-toolkit/tsconfig/tsconfig.web.json", "extends": "@electron-toolkit/tsconfig/tsconfig.web.json",
"include": [ "include": [
"src/renderer/src/env.d.ts",
"src/renderer/src/**/*", "src/renderer/src/**/*",
"src/renderer/src/**/*.tsx",
"src/preload/*.d.ts", "src/preload/*.d.ts",
"local/src/renderer/**/*", "local/src/renderer/**/*",
"packages/shared/**/*"
], ],
"compilerOptions": { "compilerOptions": {
"composite": true, "composite": true,
"jsx": "react-jsx", "jsx": "react-jsx",
"baseUrl": ".", "baseUrl": ".",
"paths": { "paths": {
"@renderer/*": [ "@renderer/*": ["src/renderer/src/*"],
"src/renderer/src/*" "@shared/*": ["packages/shared/*"]
]
} }
} }
} }