From 3290ac4b1bc0c4c8530c4fc02cfa83258b617fda Mon Sep 17 00:00:00 2001 From: Hamm Date: Fri, 4 Apr 2025 19:07:23 +0800 Subject: [PATCH] =?UTF-8?q?refactor(Constants):=20=E4=BC=98=E5=8C=96?= =?UTF-8?q?=E4=B8=80=E4=BA=9B=E5=B8=B8=E9=87=8F=E5=92=8C=E6=9E=9A=E4=B8=BE?= =?UTF-8?q?=E5=80=BC=20(#3773)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * refactor(main): 使用枚举管理 IPC 通道 - 新增 IpcChannel 枚举,用于统一管理所有的 IPC 通道 - 修改相关代码,使用 IpcChannel 枚举替代硬编码的字符串通道名称 - 此改动有助于提高代码的可维护性和可读性,避免因通道名称变更导致的错误 * refactor(ipc): 将字符串通道名称替换为 IpcChannel 枚举 - 在多个文件中将硬编码的字符串通道名称替换为 IpcChannel 枚举值 - 更新了相关文件的导入,增加了对 IpcChannel 的引用 - 通过使用枚举来管理 IPC 通道名称,提高了代码的可维护性和可读性 * refactor(ipc): 调整 IPC 通道枚举和预加载脚本 - 移除了 IpcChannel 枚举中的未使用注释 - 更新了预加载脚本中 IpcChannel 的导入路径 * refactor(ipc): 更新 IpcChannel导入路径 - 将 IpcChannel 的导入路径从 @main/enum/IpcChannel 修改为 @shared/IpcChannel - 此修改涉及多个文件,包括 AppUpdater、BackupManager、EditMcpJsonPopup 等 - 同时移除了 tsconfig.web.json 中对 src/main/**/* 的引用 * refactor(ipc): 添加 ReduxStoreReady 事件并更新事件监听 - 在 IpcChannel 枚举中添加 ReduxStoreReady 事件 - 更新 ReduxService 中的事件监听,使用新的枚举值 * refactor(main): 重构 ReduxService 中的状态变化事件处理 - 将状态变化事件名称定义为常量 STATUS_CHANGE_EVENT - 更新事件监听和触发使用新的常量 - 优化了代码结构,提高了可维护性 * refactor(i18n): 优化国际化配置和语言选择逻辑 - 在多个文件中引入 defaultLanguage 常量,统一默认语言设置 - 调整 i18n 初始化和语言变更逻辑,使用新配置 - 更新相关组件和 Hook 中的语言选择逻辑 * refactor(ConfigManager): 重构配置管理器 - 添加 ConfigKeys 枚举,用于统一配置项的键名 - 引入 defaultLanguage,作为默认语言设置 - 重构 get 和 set 方法,使用 ConfigKeys 枚举作为键名 - 优化类型定义和方法签名,提高代码可读性和可维护性 * refactor(ConfigManager): 重命名配置键 ZoomFactor 将配置键 zoomFactor 重命名为 ZoomFactor,以符合命名规范。 更新了相关方法和属性以反映这一变更。 * refactor(shared): 重构常量定义并优化文件大小格式化逻辑 - 在 constant.ts 中添加 KB、MB、GB 常量定义 - 将 defaultLanguage 移至 constant.ts - 更新 ConfigManager、useAppInit、i18n、GeneralSettings 等文件中的导入路径 - 优化 formatFileSize 函数,使用新定义的常量 * refactor(FileSize): 使用 GB/MB/KB 等常量处理文件大小计算 * refactor(ipc): 将字符串通道名称替换为 IpcChannel 枚举 - 在多个文件中将硬编码的字符串通道名称替换为 IpcChannel 枚举值 - 更新了相关文件的导入,增加了对 IpcChannel 的引用 - 通过使用枚举来管理 IPC 通道名称,提高了代码的可维护性和可读性 * refactor(ipc): 更新 IpcChannel导入路径 - 将 IpcChannel 的导入路径从 @main/enum/IpcChannel 修改为 @shared/IpcChannel - 此修改涉及多个文件,包括 AppUpdater、BackupManager、EditMcpJsonPopup 等 - 同时移除了 tsconfig.web.json 中对 src/main/**/* 的引用 * refactor(i18n): 优化国际化配置和语言选择逻辑 - 在多个文件中引入 defaultLanguage 常量,统一默认语言设置 - 调整 i18n 初始化和语言变更逻辑,使用新配置 - 更新相关组件和 Hook 中的语言选择逻辑 * refactor(shared): 重构常量定义并优化文件大小格式化逻辑 - 在 constant.ts 中添加 KB、MB、GB 常量定义 - 将 defaultLanguage 移至 constant.ts - 更新 ConfigManager、useAppInit、i18n、GeneralSettings 等文件中的导入路径 - 优化 formatFileSize 函数,使用新定义的常量 * refactor: 移除重复的导入语句 - 在 HomeWindow.tsx 和 useAppInit.ts 文件中移除了重复的 defaultLanguage导入语句 - 这个改动简化了代码结构,提高了代码的可读性和维护性 --- packages/shared/IpcChannel.ts | 150 ++++++++++++++ packages/shared/config/constant.ts | 5 + src/main/index.ts | 3 +- src/main/ipc.ts | 191 +++++++++--------- src/main/services/AppUpdater.ts | 13 +- src/main/services/BackupManager.ts | 5 +- src/main/services/ConfigManager.ts | 66 +++--- src/main/services/FileStorage.ts | 4 +- src/main/services/KnowledgeService.ts | 10 +- src/main/services/ReduxService.ts | 87 ++++---- src/main/services/WindowService.ts | 19 +- src/preload/index.ts | 188 ++++++++--------- .../src/components/Popups/BackupPopup.tsx | 3 +- .../src/components/Popups/RestorePopup.tsx | 3 +- src/renderer/src/context/ThemeProvider.tsx | 3 +- src/renderer/src/hooks/useAppInit.ts | 4 +- src/renderer/src/hooks/useFullScreenNotice.ts | 3 +- src/renderer/src/hooks/useKnowledge.ts | 3 +- src/renderer/src/hooks/useMCPServers.ts | 3 +- src/renderer/src/hooks/useUpdateHandler.ts | 13 +- src/renderer/src/i18n/index.ts | 6 +- src/renderer/src/pages/files/GeminiFiles.tsx | 3 +- .../src/pages/settings/GeneralSettings.tsx | 3 +- .../providers/AiProvider/GeminiProvider.ts | 3 +- src/renderer/src/store/llm.ts | 3 +- src/renderer/src/store/settings.ts | 3 +- src/renderer/src/utils/index.ts | 11 +- .../src/windows/mini/home/HomeWindow.tsx | 13 +- 28 files changed, 518 insertions(+), 303 deletions(-) create mode 100644 packages/shared/IpcChannel.ts diff --git a/packages/shared/IpcChannel.ts b/packages/shared/IpcChannel.ts new file mode 100644 index 00000000..57bf8967 --- /dev/null +++ b/packages/shared/IpcChannel.ts @@ -0,0 +1,150 @@ +export enum IpcChannel { + App_ClearCache = 'app:clear-cache', + App_SetLaunchOnBoot = 'app:set-launch-on-boot', + App_SetLanguage = 'app:set-language', + App_ShowUpdateDialog = 'app:show-update-dialog', + App_CheckForUpdate = 'app:check-for-update', + App_Reload = 'app:reload', + App_Info = 'app:info', + App_Proxy = 'app:proxy', + App_SetLaunchToTray = 'app:set-launch-to-tray', + App_SetTray = 'app:set-tray', + App_SetTrayOnClose = 'app:set-tray-on-close', + App_RestartTray = 'app:restart-tray', + App_SetTheme = 'app:set-theme', + + App_IsBinaryExist = 'app:is-binary-exist', + App_GetBinaryPath = 'app:get-binary-path', + App_InstallUvBinary = 'app:install-uv-binary', + App_InstallBunBinary = 'app:install-bun-binary', + + // Open + Open_Path = 'open:path', + Open_Website = 'open:website', + + Minapp = 'minapp', + + Config_Set = 'config:set', + Config_Get = 'config:get', + + MiniWindow_Show = 'miniwindow:show', + MiniWindow_Hide = 'miniwindow:hide', + MiniWindow_Close = 'miniwindow:close', + MiniWindow_Toggle = 'miniwindow:toggle', + MiniWindow_SetPin = 'miniwindow:set-pin', + + // Mcp + Mcp_RemoveServer = 'mcp:remove-server', + Mcp_RestartServer = 'mcp:restart-server', + Mcp_StopServer = 'mcp:stop-server', + Mcp_ListTools = 'mcp:list-tools', + Mcp_CallTool = 'mcp:call-tool', + Mcp_GetInstallInfo = 'mcp:get-install-info', + Mcp_ServersChanged = 'mcp:servers-changed', + Mcp_ServersUpdated = 'mcp:servers-updated', + + //copilot + Copilot_GetAuthMessage = 'copilot:get-auth-message', + Copilot_GetCopilotToken = 'copilot:get-copilot-token', + Copilot_SaveCopilotToken = 'copilot:save-copilot-token', + Copilot_GetToken = 'copilot:get-token', + Copilot_Logout = 'copilot:logout', + Copilot_GetUser = 'copilot:get-user', + + // obsidian + Obsidian_GetVaults = 'obsidian:get-vaults', + Obsidian_GetFiles = 'obsidian:get-files', + + // nutstore + Nutstore_GetSsoUrl = 'nutstore:get-sso-url', + Nutstore_DecryptToken = 'nutstore:decrypt-token', + Nutstore_GetDirectoryContents = 'nutstore:get-directory-contents', + + //aes + Aes_Encrypt = 'aes:encrypt', + Aes_Decrypt = 'aes:decrypt', + + Gemini_UploadFile = 'gemini:upload-file', + Gemini_Base64File = 'gemini:base64-file', + Gemini_RetrieveFile = 'gemini:retrieve-file', + Gemini_ListFiles = 'gemini:list-files', + Gemini_DeleteFile = 'gemini:delete-file', + + Windows_ResetMinimumSize = 'window:reset-minimum-size', + Windows_SetMinimumSize = 'window:set-minimum-size', + + SelectionMenu_Action = 'selection-menu:action', + + KnowledgeBase_Create = 'knowledge-base:create', + KnowledgeBase_Reset = 'knowledge-base:reset', + KnowledgeBase_Delete = 'knowledge-base:delete', + KnowledgeBase_Add = 'knowledge-base:add', + KnowledgeBase_Remove = 'knowledge-base:remove', + KnowledgeBase_Search = 'knowledge-base:search', + KnowledgeBase_Rerank = 'knowledge-base:rerank', + + //file + File_Open = 'file:open', + File_OpenPath = 'file:openPath', + File_Save = 'file:save', + File_Select = 'file:select', + File_Upload = 'file:upload', + File_Clear = 'file:clear', + File_Read = 'file:read', + File_Delete = 'file:delete', + File_Get = 'file:get', + File_SelectFolder = 'file:selectFolder', + File_Create = 'file:create', + File_Write = 'file:write', + File_SaveImage = 'file:saveImage', + File_Base64Image = 'file:base64Image', + File_Download = 'file:download', + File_Copy = 'file:copy', + File_BinaryFile = 'file:binaryFile', + + Fs_Read = 'fs:read', + + Export_Word = 'export:word', + + Shortcuts_Update = 'shortcuts:update', + + // backup + Backup_Backup = 'backup:backup', + Backup_Restore = 'backup:restore', + Backup_BackupToWebdav = 'backup:backupToWebdav', + Backup_RestoreFromWebdav = 'backup:restoreFromWebdav', + Backup_ListWebdavFiles = 'backup:listWebdavFiles', + Backup_CheckConnection = 'backup:checkConnection', + Backup_CreateDirectory = 'backup:createDirectory', + + // zip + Zip_Compress = 'zip:compress', + Zip_Decompress = 'zip:decompress', + + // system + System_GetDeviceType = 'system:getDeviceType', + + // events + SelectionAction = 'selection-action', + BackupProgress = 'backup-progress', + ThemeChange = 'theme:change', + UpdateDownloadedCancelled = 'update-downloaded-cancelled', + RestoreProgress = 'restore-progress', + UpdateError = 'update-error', + UpdateAvailable = 'update-available', + UpdateNotAvailable = 'update-not-available', + DownloadProgress = 'download-progress', + UpdateDownloaded = 'update-downloaded', + DownloadUpdate = 'download-update', + + DirectoryProcessingPercent = 'directory-processing-percent', + + FullscreenStatusChanged = 'fullscreen-status-changed', + + HideMiniWindow = 'hide-mini-window', + ShowMiniWindow = 'show-mini-window', + MiniWindowReload = 'miniwindow-reload', + + ReduxStateChange = 'redux-state-change', + ReduxStoreReady = 'redux-store-ready', +} diff --git a/packages/shared/config/constant.ts b/packages/shared/config/constant.ts index 4dc39e5c..df816011 100644 --- a/packages/shared/config/constant.ts +++ b/packages/shared/config/constant.ts @@ -157,3 +157,8 @@ export const ZOOM_SHORTCUTS = [ system: true } ] + +export const KB = 1024 +export const MB = 1024 * KB +export const GB = 1024 * MB +export const defaultLanguage = 'en-US' diff --git a/src/main/index.ts b/src/main/index.ts index de9c7f9e..e753602a 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -9,6 +9,7 @@ import { CHERRY_STUDIO_PROTOCOL, handleProtocolUrl, registerProtocolClient } fro import { registerShortcuts } from './services/ShortcutService' import { TrayService } from './services/TrayService' import { windowService } from './services/WindowService' +import { IpcChannel } from '@shared/IpcChannel' // Check for single instance lock if (!app.requestSingleInstanceLock()) { @@ -52,7 +53,7 @@ if (!app.requestSingleInstanceLock()) { .then((name) => console.log(`Added Extension: ${name}`)) .catch((err) => console.log('An error occurred: ', err)) } - ipcMain.handle('system:getDeviceType', () => { + ipcMain.handle(IpcChannel.System_GetDeviceType, () => { return process.platform === 'darwin' ? 'mac' : process.platform === 'win32' ? 'windows' : 'linux' }) }) diff --git a/src/main/ipc.ts b/src/main/ipc.ts index 559e05e7..491b69ca 100644 --- a/src/main/ipc.ts +++ b/src/main/ipc.ts @@ -27,6 +27,7 @@ import { getResourcePath } from './utils' import { decrypt, encrypt } from './utils/aes' import { getFilesDir } from './utils/file' import { compress, decompress } from './utils/zip' +import { IpcChannel } from '@shared/IpcChannel' const fileManager = new FileStorage() const backupManager = new BackupManager() @@ -36,7 +37,7 @@ const obsidianVaultService = new ObsidianVaultService() export function registerIpc(mainWindow: BrowserWindow, app: Electron.App) { const appUpdater = new AppUpdater(mainWindow) - ipcMain.handle('app:info', () => ({ + ipcMain.handle(IpcChannel.App_Info, () => ({ version: app.getVersion(), isPackaged: app.isPackaged, appPath: app.getAppPath(), @@ -46,7 +47,7 @@ export function registerIpc(mainWindow: BrowserWindow, app: Electron.App) { logsPath: log.transports.file.getFile().path })) - ipcMain.handle('app:proxy', async (_, proxy: string) => { + ipcMain.handle(IpcChannel.App_Proxy, async (_, proxy: string) => { let proxyConfig: ProxyConfig if (proxy === 'system') { @@ -60,19 +61,19 @@ export function registerIpc(mainWindow: BrowserWindow, app: Electron.App) { await proxyManager.configureProxy(proxyConfig) }) - ipcMain.handle('app:reload', () => mainWindow.reload()) - ipcMain.handle('open:website', (_, url: string) => shell.openExternal(url)) + ipcMain.handle(IpcChannel.App_Reload, () => mainWindow.reload()) + ipcMain.handle(IpcChannel.Open_Website, (_, url: string) => shell.openExternal(url)) // Update - ipcMain.handle('app:show-update-dialog', () => appUpdater.showUpdateDialog(mainWindow)) + ipcMain.handle(IpcChannel.App_ShowUpdateDialog, () => appUpdater.showUpdateDialog(mainWindow)) // language - ipcMain.handle('app:set-language', (_, language) => { + ipcMain.handle(IpcChannel.App_SetLanguage, (_, language) => { configManager.setLanguage(language) }) // launch on boot - ipcMain.handle('app:set-launch-on-boot', (_, openAtLogin: boolean) => { + ipcMain.handle(IpcChannel.App_SetLaunchOnBoot, (_, openAtLogin: boolean) => { // Set login item settings for windows and mac // linux is not supported because it requires more file operations if (isWin || isMac) { @@ -81,32 +82,32 @@ export function registerIpc(mainWindow: BrowserWindow, app: Electron.App) { }) // launch to tray - ipcMain.handle('app:set-launch-to-tray', (_, isActive: boolean) => { + ipcMain.handle(IpcChannel.App_SetLaunchToTray, (_, isActive: boolean) => { configManager.setLaunchToTray(isActive) }) // tray - ipcMain.handle('app:set-tray', (_, isActive: boolean) => { + ipcMain.handle(IpcChannel.App_SetTray, (_, isActive: boolean) => { configManager.setTray(isActive) }) // to tray on close - ipcMain.handle('app:set-tray-on-close', (_, isActive: boolean) => { + ipcMain.handle(IpcChannel.App_SetTrayOnClose, (_, isActive: boolean) => { configManager.setTrayOnClose(isActive) }) - ipcMain.handle('app:restart-tray', () => TrayService.getInstance().restartTray()) + ipcMain.handle(IpcChannel.App_RestartTray, () => TrayService.getInstance().restartTray()) - ipcMain.handle('config:set', (_, key: string, value: any) => { + ipcMain.handle(IpcChannel.Config_Set, (_, key: string, value: any) => { configManager.set(key, value) }) - ipcMain.handle('config:get', (_, key: string) => { + ipcMain.handle(IpcChannel.Config_Get, (_, key: string) => { return configManager.get(key) }) // theme - ipcMain.handle('app:set-theme', (event, theme: ThemeMode) => { + ipcMain.handle(IpcChannel.App_SetTheme, (event, theme: ThemeMode) => { if (theme === configManager.getTheme()) return configManager.setTheme(theme) @@ -117,7 +118,7 @@ export function registerIpc(mainWindow: BrowserWindow, app: Electron.App) { // 向其他窗口广播主题变化 windows.forEach((win) => { if (win.webContents.id !== senderWindowId) { - win.webContents.send('theme:change', theme) + win.webContents.send(IpcChannel.ThemeChange, theme) } }) @@ -126,7 +127,7 @@ export function registerIpc(mainWindow: BrowserWindow, app: Electron.App) { }) // clear cache - ipcMain.handle('app:clear-cache', async () => { + ipcMain.handle(IpcChannel.App_ClearCache, async () => { const sessions = [session.defaultSession, session.fromPartition('persist:webview')] try { @@ -148,7 +149,7 @@ export function registerIpc(mainWindow: BrowserWindow, app: Electron.App) { }) // check for update - ipcMain.handle('app:check-for-update', async () => { + ipcMain.handle(IpcChannel.App_CheckForUpdate, async () => { const update = await appUpdater.autoUpdater.checkForUpdates() return { currentVersion: appUpdater.autoUpdater.currentVersion, @@ -157,42 +158,42 @@ export function registerIpc(mainWindow: BrowserWindow, app: Electron.App) { }) // zip - ipcMain.handle('zip:compress', (_, text: string) => compress(text)) - ipcMain.handle('zip:decompress', (_, text: Buffer) => decompress(text)) + ipcMain.handle(IpcChannel.Zip_Compress, (_, text: string) => compress(text)) + ipcMain.handle(IpcChannel.Zip_Decompress, (_, text: Buffer) => decompress(text)) // backup - ipcMain.handle('backup:backup', backupManager.backup) - ipcMain.handle('backup:restore', backupManager.restore) - ipcMain.handle('backup:backupToWebdav', backupManager.backupToWebdav) - ipcMain.handle('backup:restoreFromWebdav', backupManager.restoreFromWebdav) - ipcMain.handle('backup:listWebdavFiles', backupManager.listWebdavFiles) - ipcMain.handle('backup:checkConnection', backupManager.checkConnection) - ipcMain.handle('backup:createDirectory', backupManager.createDirectory) + ipcMain.handle(IpcChannel.Backup_Backup, backupManager.backup) + ipcMain.handle(IpcChannel.Backup_Restore, backupManager.restore) + ipcMain.handle(IpcChannel.Backup_BackupToWebdav, backupManager.backupToWebdav) + ipcMain.handle(IpcChannel.Backup_RestoreFromWebdav, backupManager.restoreFromWebdav) + ipcMain.handle(IpcChannel.Backup_ListWebdavFiles, backupManager.listWebdavFiles) + ipcMain.handle(IpcChannel.Backup_CheckConnection, backupManager.checkConnection) + ipcMain.handle(IpcChannel.Backup_CreateDirectory, backupManager.createDirectory) // file - ipcMain.handle('file:open', fileManager.open) - ipcMain.handle('file:openPath', fileManager.openPath) - ipcMain.handle('file:save', fileManager.save) - ipcMain.handle('file:select', fileManager.selectFile) - ipcMain.handle('file:upload', fileManager.uploadFile) - ipcMain.handle('file:clear', fileManager.clear) - ipcMain.handle('file:read', fileManager.readFile) - ipcMain.handle('file:delete', fileManager.deleteFile) - ipcMain.handle('file:get', fileManager.getFile) - ipcMain.handle('file:selectFolder', fileManager.selectFolder) - ipcMain.handle('file:create', fileManager.createTempFile) - ipcMain.handle('file:write', fileManager.writeFile) - ipcMain.handle('file:saveImage', fileManager.saveImage) - ipcMain.handle('file:base64Image', fileManager.base64Image) - ipcMain.handle('file:download', fileManager.downloadFile) - ipcMain.handle('file:copy', fileManager.copyFile) - ipcMain.handle('file:binaryFile', fileManager.binaryFile) + ipcMain.handle(IpcChannel.File_Open, fileManager.open) + ipcMain.handle(IpcChannel.File_OpenPath, fileManager.openPath) + ipcMain.handle(IpcChannel.File_Save, fileManager.save) + ipcMain.handle(IpcChannel.File_Select, fileManager.selectFile) + ipcMain.handle(IpcChannel.File_Upload, fileManager.uploadFile) + ipcMain.handle(IpcChannel.File_Clear, fileManager.clear) + ipcMain.handle(IpcChannel.File_Read, fileManager.readFile) + ipcMain.handle(IpcChannel.File_Delete, fileManager.deleteFile) + ipcMain.handle(IpcChannel.File_Get, fileManager.getFile) + ipcMain.handle(IpcChannel.File_SelectFolder, fileManager.selectFolder) + ipcMain.handle(IpcChannel.File_Create, fileManager.createTempFile) + ipcMain.handle(IpcChannel.File_Write, fileManager.writeFile) + ipcMain.handle(IpcChannel.File_SaveImage, fileManager.saveImage) + ipcMain.handle(IpcChannel.File_Base64Image, fileManager.base64Image) + ipcMain.handle(IpcChannel.File_Download, fileManager.downloadFile) + ipcMain.handle(IpcChannel.File_Copy, fileManager.copyFile) + ipcMain.handle(IpcChannel.File_BinaryFile, fileManager.binaryFile) // fs - ipcMain.handle('fs:read', FileService.readFile) + ipcMain.handle(IpcChannel.Fs_Read, FileService.readFile) // minapp - ipcMain.handle('minapp', (_, args) => { + ipcMain.handle(IpcChannel.Minapp, (_, args) => { windowService.createMinappWindow({ url: args.url, parent: mainWindow, @@ -204,15 +205,15 @@ export function registerIpc(mainWindow: BrowserWindow, app: Electron.App) { }) // export - ipcMain.handle('export:word', exportService.exportToWord) + ipcMain.handle(IpcChannel.Export_Word, exportService.exportToWord) // open path - ipcMain.handle('open:path', async (_, path: string) => { + ipcMain.handle(IpcChannel.Open_Path, async (_, path: string) => { await shell.openPath(path) }) // shortcuts - ipcMain.handle('shortcuts:update', (_, shortcuts: Shortcut[]) => { + ipcMain.handle(IpcChannel.Shortcuts_Update, (_, shortcuts: Shortcut[]) => { configManager.setShortcuts(shortcuts) // Refresh shortcuts registration if (mainWindow) { @@ -222,20 +223,20 @@ export function registerIpc(mainWindow: BrowserWindow, app: Electron.App) { }) // knowledge base - ipcMain.handle('knowledge-base:create', KnowledgeService.create) - ipcMain.handle('knowledge-base:reset', KnowledgeService.reset) - ipcMain.handle('knowledge-base:delete', KnowledgeService.delete) - ipcMain.handle('knowledge-base:add', KnowledgeService.add) - ipcMain.handle('knowledge-base:remove', KnowledgeService.remove) - ipcMain.handle('knowledge-base:search', KnowledgeService.search) - ipcMain.handle('knowledge-base:rerank', KnowledgeService.rerank) + ipcMain.handle(IpcChannel.KnowledgeBase_Create, KnowledgeService.create) + ipcMain.handle(IpcChannel.KnowledgeBase_Reset, KnowledgeService.reset) + ipcMain.handle(IpcChannel.KnowledgeBase_Delete, KnowledgeService.delete) + ipcMain.handle(IpcChannel.KnowledgeBase_Add, KnowledgeService.add) + ipcMain.handle(IpcChannel.KnowledgeBase_Remove, KnowledgeService.remove) + ipcMain.handle(IpcChannel.KnowledgeBase_Search, KnowledgeService.search) + ipcMain.handle(IpcChannel.KnowledgeBase_Rerank, KnowledgeService.rerank) // window - ipcMain.handle('window:set-minimum-size', (_, width: number, height: number) => { + ipcMain.handle(IpcChannel.Windows_SetMinimumSize, (_, width: number, height: number) => { mainWindow?.setMinimumSize(width, height) }) - ipcMain.handle('window:reset-minimum-size', () => { + ipcMain.handle(IpcChannel.Windows_ResetMinimumSize, () => { mainWindow?.setMinimumSize(1080, 600) const [width, height] = mainWindow?.getSize() ?? [1080, 600] if (width < 1080) { @@ -244,59 +245,69 @@ export function registerIpc(mainWindow: BrowserWindow, app: Electron.App) { }) // gemini - ipcMain.handle('gemini:upload-file', GeminiService.uploadFile) - ipcMain.handle('gemini:base64-file', GeminiService.base64File) - ipcMain.handle('gemini:retrieve-file', GeminiService.retrieveFile) - ipcMain.handle('gemini:list-files', GeminiService.listFiles) - ipcMain.handle('gemini:delete-file', GeminiService.deleteFile) + ipcMain.handle(IpcChannel.Gemini_UploadFile, GeminiService.uploadFile) + ipcMain.handle(IpcChannel.Gemini_Base64File, GeminiService.base64File) + ipcMain.handle(IpcChannel.Gemini_RetrieveFile, GeminiService.retrieveFile) + ipcMain.handle(IpcChannel.Gemini_ListFiles, GeminiService.listFiles) + ipcMain.handle(IpcChannel.Gemini_DeleteFile, GeminiService.deleteFile) // mini window - ipcMain.handle('miniwindow:show', () => windowService.showMiniWindow()) - ipcMain.handle('miniwindow:hide', () => windowService.hideMiniWindow()) - ipcMain.handle('miniwindow:close', () => windowService.closeMiniWindow()) - ipcMain.handle('miniwindow:toggle', () => windowService.toggleMiniWindow()) - ipcMain.handle('miniwindow:set-pin', (_, isPinned) => windowService.setPinMiniWindow(isPinned)) + ipcMain.handle(IpcChannel.MiniWindow_Show, () => windowService.showMiniWindow()) + ipcMain.handle(IpcChannel.MiniWindow_Hide, () => windowService.hideMiniWindow()) + ipcMain.handle(IpcChannel.MiniWindow_Close, () => windowService.closeMiniWindow()) + ipcMain.handle(IpcChannel.MiniWindow_Toggle, () => windowService.toggleMiniWindow()) + ipcMain.handle(IpcChannel.MiniWindow_SetPin, (_, isPinned) => windowService.setPinMiniWindow(isPinned)) // aes - ipcMain.handle('aes:encrypt', (_, text: string, secretKey: string, iv: string) => encrypt(text, secretKey, iv)) - ipcMain.handle('aes:decrypt', (_, encryptedData: string, iv: string, secretKey: string) => + ipcMain.handle(IpcChannel.Aes_Encrypt, (_, text: string, secretKey: string, iv: string) => + encrypt(text, secretKey, iv) + ) + ipcMain.handle(IpcChannel.Aes_Decrypt, (_, encryptedData: string, iv: string, secretKey: string) => decrypt(encryptedData, iv, secretKey) ) // Register MCP handlers - ipcMain.handle('mcp:remove-server', mcpService.removeServer) - ipcMain.handle('mcp:restart-server', mcpService.restartServer) - ipcMain.handle('mcp:stop-server', mcpService.stopServer) - ipcMain.handle('mcp:list-tools', mcpService.listTools) - ipcMain.handle('mcp:call-tool', mcpService.callTool) - ipcMain.handle('mcp:get-install-info', mcpService.getInstallInfo) + ipcMain.handle(IpcChannel.Mcp_RemoveServer, mcpService.removeServer) + ipcMain.handle(IpcChannel.Mcp_RestartServer, mcpService.restartServer) + ipcMain.handle(IpcChannel.Mcp_StopServer, mcpService.stopServer) + ipcMain.handle(IpcChannel.Mcp_ListTools, mcpService.listTools) + ipcMain.handle(IpcChannel.Mcp_CallTool, mcpService.callTool) + ipcMain.handle(IpcChannel.Mcp_GetInstallInfo, mcpService.getInstallInfo) - ipcMain.handle('app:is-binary-exist', (_, name: string) => isBinaryExists(name)) - ipcMain.handle('app:get-binary-path', (_, name: string) => getBinaryPath(name)) - ipcMain.handle('app:install-uv-binary', () => runInstallScript('install-uv.js')) - ipcMain.handle('app:install-bun-binary', () => runInstallScript('install-bun.js')) + ipcMain.handle(IpcChannel.App_IsBinaryExist, (_, name: string) => isBinaryExists(name)) + ipcMain.handle(IpcChannel.App_GetBinaryPath, (_, name: string) => getBinaryPath(name)) + ipcMain.handle(IpcChannel.App_InstallUvBinary, () => runInstallScript('install-uv.js')) + ipcMain.handle(IpcChannel.App_InstallBunBinary, () => runInstallScript('install-bun.js')) //copilot - ipcMain.handle('copilot:get-auth-message', CopilotService.getAuthMessage) - ipcMain.handle('copilot:get-copilot-token', CopilotService.getCopilotToken) - ipcMain.handle('copilot:save-copilot-token', CopilotService.saveCopilotToken) - ipcMain.handle('copilot:get-token', CopilotService.getToken) - ipcMain.handle('copilot:logout', CopilotService.logout) - ipcMain.handle('copilot:get-user', CopilotService.getUser) + ipcMain.handle(IpcChannel.Copilot_GetAuthMessage, CopilotService.getAuthMessage) + ipcMain.handle(IpcChannel.Copilot_GetCopilotToken, CopilotService.getCopilotToken) + ipcMain.handle(IpcChannel.Copilot_SaveCopilotToken, CopilotService.saveCopilotToken) + ipcMain.handle(IpcChannel.Copilot_GetToken, CopilotService.getToken) + ipcMain.handle(IpcChannel.Copilot_Logout, CopilotService.logout) + ipcMain.handle(IpcChannel.Copilot_GetUser, CopilotService.getUser) // Obsidian service - ipcMain.handle('obsidian:get-vaults', () => { + ipcMain.handle(IpcChannel.Obsidian_GetVaults, () => { return obsidianVaultService.getVaults() }) - ipcMain.handle('obsidian:get-files', (_event, vaultName) => { + ipcMain.handle(IpcChannel.Obsidian_GetFiles, (_event, vaultName) => { return obsidianVaultService.getFilesByVaultName(vaultName) }) // nutstore - ipcMain.handle('nutstore:get-sso-url', NutstoreService.getNutstoreSSOUrl) - ipcMain.handle('nutstore:decrypt-token', (_, token: string) => NutstoreService.decryptToken(token)) - ipcMain.handle('nutstore:get-directory-contents', (_, token: string, path: string) => + ipcMain.handle(IpcChannel.Nutstore_GetSsoUrl, NutstoreService.getNutstoreSSOUrl) + ipcMain.handle(IpcChannel.Nutstore_DecryptToken, (_, token: string) => NutstoreService.decryptToken(token)) + ipcMain.handle(IpcChannel.Nutstore_GetDirectoryContents, (_, token: string, path: string) => NutstoreService.getDirectoryContents(token, path) ) } + + // Listen for changes in MCP servers and notify renderer + mcpService.on('servers-updated', (servers) => { + mainWindow?.webContents.send(IpcChannel.Mcp_ServersUpdated, servers) + }) + +app.on('before-quit', () => mcpService.cleanup()) + \ No newline at end of file diff --git a/src/main/services/AppUpdater.ts b/src/main/services/AppUpdater.ts index 652fb3fb..3be259bc 100644 --- a/src/main/services/AppUpdater.ts +++ b/src/main/services/AppUpdater.ts @@ -4,6 +4,7 @@ import logger from 'electron-log' import { AppUpdater as _AppUpdater, autoUpdater } from 'electron-updater' import icon from '../../../build/icon.png?asset' +import { IpcChannel } from '@shared/IpcChannel' export default class AppUpdater { autoUpdater: _AppUpdater = autoUpdater @@ -24,27 +25,27 @@ export default class AppUpdater { stack: error.stack, time: new Date().toISOString() }) - mainWindow.webContents.send('update-error', error) + mainWindow.webContents.send(IpcChannel.UpdateError, error) }) autoUpdater.on('update-available', (releaseInfo: UpdateInfo) => { logger.info('检测到新版本', releaseInfo) - mainWindow.webContents.send('update-available', releaseInfo) + mainWindow.webContents.send(IpcChannel.UpdateAvailable, releaseInfo) }) // 检测到不需要更新时 autoUpdater.on('update-not-available', () => { - mainWindow.webContents.send('update-not-available') + mainWindow.webContents.send(IpcChannel.UpdateNotAvailable) }) // 更新下载进度 autoUpdater.on('download-progress', (progress) => { - mainWindow.webContents.send('download-progress', progress) + mainWindow.webContents.send(IpcChannel.DownloadProgress, progress) }) // 当需要更新的内容下载完成后 autoUpdater.on('update-downloaded', (releaseInfo: UpdateInfo) => { - mainWindow.webContents.send('update-downloaded', releaseInfo) + mainWindow.webContents.send(IpcChannel.UpdateDownloaded, releaseInfo) this.releaseInfo = releaseInfo logger.info('下载完成', releaseInfo) }) @@ -73,7 +74,7 @@ export default class AppUpdater { app.isQuitting = true setImmediate(() => autoUpdater.quitAndInstall()) } else { - mainWindow.webContents.send('update-downloaded-cancelled') + mainWindow.webContents.send(IpcChannel.UpdateDownloadedCancelled) } }) } diff --git a/src/main/services/BackupManager.ts b/src/main/services/BackupManager.ts index a97b37e6..e3c26fdd 100644 --- a/src/main/services/BackupManager.ts +++ b/src/main/services/BackupManager.ts @@ -9,6 +9,7 @@ import { createClient, CreateDirectoryOptions, FileStat } from 'webdav' import WebDav from './WebDav' import { windowService } from './WindowService' +import { IpcChannel } from '@shared/IpcChannel' class BackupManager { private tempDir = path.join(app.getPath('temp'), 'cherry-studio', 'backup', 'temp') @@ -79,7 +80,7 @@ class BackupManager { const mainWindow = windowService.getMainWindow() const onProgress = (processData: { stage: string; progress: number; total: number }) => { - mainWindow?.webContents.send('backup-progress', processData) + mainWindow?.webContents.send(IpcChannel.BackupProgress, processData) Logger.log('[BackupManager] backup progress', processData) } @@ -139,7 +140,7 @@ class BackupManager { const mainWindow = windowService.getMainWindow() const onProgress = (processData: { stage: string; progress: number; total: number }) => { - mainWindow?.webContents.send('restore-progress', processData) + mainWindow?.webContents.send(IpcChannel.RestoreProgress, processData) Logger.log('[BackupManager] restore progress', processData) } diff --git a/src/main/services/ConfigManager.ts b/src/main/services/ConfigManager.ts index da20bc41..2dd53a02 100644 --- a/src/main/services/ConfigManager.ts +++ b/src/main/services/ConfigManager.ts @@ -1,10 +1,22 @@ -import { ZOOM_SHORTCUTS } from '@shared/config/constant' +import { defaultLanguage, ZOOM_SHORTCUTS } from '@shared/config/constant' import { LanguageVarious, Shortcut, ThemeMode } from '@types' import { app } from 'electron' import Store from 'electron-store' import { locales } from '../utils/locales' +enum ConfigKeys { + Language = 'language', + Theme = 'theme', + LaunchToTray = 'launchToTray', + Tray = 'tray', + TrayOnClose = 'trayOnClose', + ZoomFactor = 'ZoomFactor', + Shortcuts = 'shortcuts', + ClickTrayToShowQuickAssistant = 'clickTrayToShowQuickAssistant', + EnableQuickAssistant = 'enableQuickAssistant' +} + export class ConfigManager { private store: Store private subscribers: Map void>> = new Map() @@ -14,54 +26,54 @@ export class ConfigManager { } getLanguage(): LanguageVarious { - const locale = Object.keys(locales).includes(app.getLocale()) ? app.getLocale() : 'en-US' - return this.store.get('language', locale) as LanguageVarious + const locale = Object.keys(locales).includes(app.getLocale()) ? app.getLocale() : defaultLanguage + return this.get(ConfigKeys.Language, locale) as LanguageVarious } setLanguage(theme: LanguageVarious) { - this.store.set('language', theme) + this.set(ConfigKeys.Language, theme) } getTheme(): ThemeMode { - return this.store.get('theme', ThemeMode.light) as ThemeMode + return this.get(ConfigKeys.Theme, ThemeMode.light) } setTheme(theme: ThemeMode) { - this.store.set('theme', theme) + this.set(ConfigKeys.Theme, theme) } getLaunchToTray(): boolean { - return !!this.store.get('launchToTray', false) + return !!this.get(ConfigKeys.LaunchToTray, false) } setLaunchToTray(value: boolean) { - this.store.set('launchToTray', value) + this.set(ConfigKeys.LaunchToTray, value) } getTray(): boolean { - return !!this.store.get('tray', true) + return !!this.get(ConfigKeys.Tray, true) } setTray(value: boolean) { - this.store.set('tray', value) - this.notifySubscribers('tray', value) + this.set(ConfigKeys.Tray, value) + this.notifySubscribers(ConfigKeys.Tray, value) } getTrayOnClose(): boolean { - return !!this.store.get('trayOnClose', true) + return !!this.get(ConfigKeys.TrayOnClose, true) } setTrayOnClose(value: boolean) { - this.store.set('trayOnClose', value) + this.set(ConfigKeys.TrayOnClose, value) } getZoomFactor(): number { - return this.store.get('zoomFactor', 1) as number + return this.get(ConfigKeys.ZoomFactor, 1) } setZoomFactor(factor: number) { - this.store.set('zoomFactor', factor) - this.notifySubscribers('zoomFactor', factor) + this.set(ConfigKeys.ZoomFactor, factor) + this.notifySubscribers(ConfigKeys.ZoomFactor, factor) } subscribe(key: string, callback: (newValue: T) => void) { @@ -89,39 +101,39 @@ export class ConfigManager { } getShortcuts() { - return this.store.get('shortcuts', ZOOM_SHORTCUTS) as Shortcut[] | [] + return this.get(ConfigKeys.Shortcuts, ZOOM_SHORTCUTS) as Shortcut[] | [] } setShortcuts(shortcuts: Shortcut[]) { - this.store.set( - 'shortcuts', + this.set( + ConfigKeys.Shortcuts, shortcuts.filter((shortcut) => shortcut.system) ) - this.notifySubscribers('shortcuts', shortcuts) + this.notifySubscribers(ConfigKeys.Shortcuts, shortcuts) } getClickTrayToShowQuickAssistant(): boolean { - return this.store.get('clickTrayToShowQuickAssistant', false) as boolean + return this.get(ConfigKeys.ClickTrayToShowQuickAssistant, false) } setClickTrayToShowQuickAssistant(value: boolean) { - this.store.set('clickTrayToShowQuickAssistant', value) + this.set(ConfigKeys.ClickTrayToShowQuickAssistant, value) } getEnableQuickAssistant(): boolean { - return this.store.get('enableQuickAssistant', false) as boolean + return this.get(ConfigKeys.EnableQuickAssistant, false) } setEnableQuickAssistant(value: boolean) { - this.store.set('enableQuickAssistant', value) + this.set(ConfigKeys.EnableQuickAssistant, value) } - set(key: string, value: any) { + set(key: string, value: unknown) { this.store.set(key, value) } - get(key: string) { - return this.store.get(key) + get(key: string, defaultValue?: T) { + return this.store.get(key, defaultValue) as T } } diff --git a/src/main/services/FileStorage.ts b/src/main/services/FileStorage.ts index 9557e17c..accad809 100644 --- a/src/main/services/FileStorage.ts +++ b/src/main/services/FileStorage.ts @@ -1,5 +1,5 @@ import { getFilesDir, getFileType, getTempDir } from '@main/utils/file' -import { documentExts, imageExts } from '@shared/config/constant' +import { documentExts, imageExts, MB } from '@shared/config/constant' import { FileType } from '@types' import * as crypto from 'crypto' import { @@ -122,7 +122,7 @@ class FileStorage { private async compressImage(sourcePath: string, destPath: string): Promise { try { const stats = fs.statSync(sourcePath) - const fileSizeInMB = stats.size / (1024 * 1024) + const fileSizeInMB = stats.size / MB // 如果图片大于1MB才进行压缩 if (fileSizeInMB > 1) { diff --git a/src/main/services/KnowledgeService.ts b/src/main/services/KnowledgeService.ts index 0e8cb9de..d282b278 100644 --- a/src/main/services/KnowledgeService.ts +++ b/src/main/services/KnowledgeService.ts @@ -31,6 +31,8 @@ import { FileType, KnowledgeBaseParams, KnowledgeItem } from '@types' import { app } from 'electron' import Logger from 'electron-log' import { v4 as uuidv4 } from 'uuid' +import { IpcChannel } from '@shared/IpcChannel' +import { MB } from '@shared/config/constant' export interface KnowledgeBaseAddItemOptions { base: KnowledgeBaseParams @@ -91,7 +93,7 @@ class KnowledgeService { private workload = 0 private processingItemCount = 0 private knowledgeItemProcessingQueueMappingPromise: Map void> = new Map() - private static MAXIMUM_WORKLOAD = 1024 * 1024 * 80 + private static MAXIMUM_WORKLOAD = 80 * MB private static MAXIMUM_PROCESSING_ITEM_COUNT = 30 private static ERROR_LOADER_RETURN: LoaderReturn = { entriesAdded: 0, uniqueId: '', uniqueIds: [''], loaderType: '' } @@ -194,7 +196,7 @@ class KnowledgeService { const sendDirectoryProcessingPercent = (totalFiles: number, processedFiles: number) => { const mainWindow = windowService.getMainWindow() - mainWindow?.webContents.send('directory-processing-percent', { + mainWindow?.webContents.send(IpcChannel.DirectoryProcessingPercent, { itemId: item.id, percent: (processedFiles / totalFiles) * 100 }) @@ -270,7 +272,7 @@ class KnowledgeService { return KnowledgeService.ERROR_LOADER_RETURN }) }, - evaluateTaskWorkload: { workload: 1024 * 1024 * 2 } + evaluateTaskWorkload: { workload: 2 * MB } } ], loaderDoneReturn: null @@ -309,7 +311,7 @@ class KnowledgeService { Logger.error(err) return KnowledgeService.ERROR_LOADER_RETURN }), - evaluateTaskWorkload: { workload: 1024 * 1024 * 20 } + evaluateTaskWorkload: { workload: 20 * MB } } ], loaderDoneReturn: null diff --git a/src/main/services/ReduxService.ts b/src/main/services/ReduxService.ts index b1a4baab..479f62da 100644 --- a/src/main/services/ReduxService.ts +++ b/src/main/services/ReduxService.ts @@ -2,6 +2,7 @@ import { ipcMain } from 'electron' import { EventEmitter } from 'events' import { windowService } from './WindowService' +import { IpcChannel } from '@shared/IpcChannel' type StoreValue = any type Unsubscribe = () => void @@ -10,6 +11,8 @@ export class ReduxService extends EventEmitter { private stateCache: any = {} private isReady = false + private readonly STATUS_CHANGE_EVENT = 'statusChange' + constructor() { super() this.setupIpcHandlers() @@ -17,15 +20,15 @@ export class ReduxService extends EventEmitter { private setupIpcHandlers() { // 监听 store 就绪事件 - ipcMain.handle('redux-store-ready', () => { + ipcMain.handle(IpcChannel.ReduxStoreReady, () => { this.isReady = true this.emit('ready') }) // 监听 store 状态变化 - ipcMain.on('redux-state-change', (_, newState) => { + ipcMain.on(IpcChannel.ReduxStateChange, (_, newState) => { this.stateCache = newState - this.emit('stateChange', newState) + this.emit(this.STATUS_CHANGE_EVENT, newState) }) } @@ -122,19 +125,23 @@ export class ReduxService extends EventEmitter { await this.waitForStoreReady(mainWindow.webContents) // 在渲染进程中设置监听 - await mainWindow.webContents.executeJavaScript(` + await mainWindow.webContents.executeJavaScript( + ` if (!window._storeSubscriptions) { window._storeSubscriptions = new Set(); // 设置全局状态变化监听 const unsubscribe = window.store.subscribe(() => { const state = window.store.getState(); - window.electron.ipcRenderer.send('redux-state-change', state); + window.electron.ipcRenderer.send('` + + IpcChannel.ReduxStateChange + + `', state); }); window._storeSubscriptions.add(unsubscribe); } - `) + ` + ) // 在主进程中处理回调 const handler = async () => { @@ -146,9 +153,9 @@ export class ReduxService extends EventEmitter { } } - this.on('stateChange', handler) + this.on(this.STATUS_CHANGE_EVENT, handler) return () => { - this.off('stateChange', handler) + this.off(this.STATUS_CHANGE_EVENT, handler) } } @@ -180,41 +187,41 @@ export class ReduxService extends EventEmitter { export const reduxService = new ReduxService() /** example -async function example() { - try { - // 读取状态 - const settings = await reduxService.select('state.settings') - console.log('settings', settings) + async function example() { + try { + // 读取状态 + const settings = await reduxService.select('state.settings') + console.log('settings', settings) - // 派发 action - await reduxService.dispatch({ - type: 'settings/updateApiKey', - payload: 'new-api-key' - }) + // 派发 action + await reduxService.dispatch({ + type: 'settings/updateApiKey', + payload: 'new-api-key' + }) - // 订阅状态变化 - const unsubscribe = await reduxService.subscribe('state.settings.apiKey', (newValue) => { - console.log('API key changed:', newValue) - }) + // 订阅状态变化 + const unsubscribe = await reduxService.subscribe('state.settings.apiKey', (newValue) => { + console.log('API key changed:', newValue) + }) - // 批量执行 actions - await reduxService.batch([ - { type: 'action1', payload: 'data1' }, - { type: 'action2', payload: 'data2' } - ]) + // 批量执行 actions + await reduxService.batch([ + { type: 'action1', payload: 'data1' }, + { type: 'action2', payload: 'data2' } + ]) - // 同步方法虽然可能不是最新的数据,但响应更快 - const apiKey = reduxService.selectSync('state.settings.apiKey') - console.log('apiKey', apiKey) + // 同步方法虽然可能不是最新的数据,但响应更快 + const apiKey = reduxService.selectSync('state.settings.apiKey') + console.log('apiKey', apiKey) - // 处理保证是最新的数据 - const apiKey1 = await reduxService.select('state.settings.apiKey') - console.log('apiKey1', apiKey1) + // 处理保证是最新的数据 + const apiKey1 = await reduxService.select('state.settings.apiKey') + console.log('apiKey1', apiKey1) - // 取消订阅 - unsubscribe() - } catch (error) { - console.error('Error:', error) - } -} -*/ + // 取消订阅 + unsubscribe() + } catch (error) { + console.error('Error:', error) + } + } + */ diff --git a/src/main/services/WindowService.ts b/src/main/services/WindowService.ts index 0ab3b023..966cc6f2 100644 --- a/src/main/services/WindowService.ts +++ b/src/main/services/WindowService.ts @@ -10,6 +10,7 @@ import icon from '../../../build/icon.png?asset' import { titleBarOverlayDark, titleBarOverlayLight } from '../config' import { locales } from '../utils/locales' import { configManager } from './ConfigManager' +import { IpcChannel } from '@shared/IpcChannel' export class WindowService { private static instance: WindowService | null = null @@ -168,12 +169,12 @@ export class WindowService { // 处理全屏相关事件 mainWindow.on('enter-full-screen', () => { this.wasFullScreen = true - mainWindow.webContents.send('fullscreen-status-changed', true) + mainWindow.webContents.send(IpcChannel.FullscreenStatusChanged, true) }) mainWindow.on('leave-full-screen', () => { this.wasFullScreen = false - mainWindow.webContents.send('fullscreen-status-changed', false) + mainWindow.webContents.send(IpcChannel.FullscreenStatusChanged, false) }) // set the zoom factor again when the window is going to resize @@ -434,14 +435,14 @@ export class WindowService { }) this.miniWindow.on('hide', () => { - this.miniWindow?.webContents.send('hide-mini-window') + this.miniWindow?.webContents.send(IpcChannel.HideMiniWindow) }) this.miniWindow.on('show', () => { - this.miniWindow?.webContents.send('show-mini-window') + this.miniWindow?.webContents.send(IpcChannel.ShowMiniWindow) }) - ipcMain.on('miniwindow-reload', () => { + ipcMain.on(IpcChannel.MiniWindowReload, () => { this.miniWindow?.reload() }) @@ -547,7 +548,7 @@ export class WindowService { // 点击其他地方时隐藏窗口 this.selectionMenuWindow.on('blur', () => { this.selectionMenuWindow?.hide() - this.miniWindow?.webContents.send('selection-action', { + this.miniWindow?.webContents.send(IpcChannel.SelectionAction, { action: 'home', selectedText: this.lastSelectedText }) @@ -565,12 +566,12 @@ export class WindowService { private setupSelectionMenuEvents() { if (!this.selectionMenuWindow) return - ipcMain.removeHandler('selection-menu:action') - ipcMain.handle('selection-menu:action', (_, action) => { + ipcMain.removeHandler(IpcChannel.SelectionMenu_Action) + ipcMain.handle(IpcChannel.SelectionMenu_Action, (_, action) => { this.selectionMenuWindow?.hide() this.showMiniWindow() setTimeout(() => { - this.miniWindow?.webContents.send('selection-action', { + this.miniWindow?.webContents.send(IpcChannel.SelectionAction, { action, selectedText: this.lastSelectedText }) diff --git a/src/preload/index.ts b/src/preload/index.ts index fe223e9f..899d3e87 100644 --- a/src/preload/index.ts +++ b/src/preload/index.ts @@ -3,77 +3,78 @@ import { electronAPI } from '@electron-toolkit/preload' import { FileType, KnowledgeBaseParams, KnowledgeItem, MCPServer, Shortcut, WebDavConfig } from '@types' import { contextBridge, ipcRenderer, OpenDialogOptions, shell } from 'electron' import { CreateDirectoryOptions } from 'webdav' +import { IpcChannel } from '@shared/IpcChannel' // Custom APIs for renderer const api = { - getAppInfo: () => ipcRenderer.invoke('app:info'), - reload: () => ipcRenderer.invoke('app:reload'), - setProxy: (proxy: string) => ipcRenderer.invoke('app:proxy', proxy), - checkForUpdate: () => ipcRenderer.invoke('app:check-for-update'), - showUpdateDialog: () => ipcRenderer.invoke('app:show-update-dialog'), - setLanguage: (lang: string) => ipcRenderer.invoke('app:set-language', lang), - setLaunchOnBoot: (isActive: boolean) => ipcRenderer.invoke('app:set-launch-on-boot', isActive), - setLaunchToTray: (isActive: boolean) => ipcRenderer.invoke('app:set-launch-to-tray', isActive), - setTray: (isActive: boolean) => ipcRenderer.invoke('app:set-tray', isActive), - setTrayOnClose: (isActive: boolean) => ipcRenderer.invoke('app:set-tray-on-close', isActive), - restartTray: () => ipcRenderer.invoke('app:restart-tray'), - setTheme: (theme: 'light' | 'dark') => ipcRenderer.invoke('app:set-theme', theme), - openWebsite: (url: string) => ipcRenderer.invoke('open:website', url), - minApp: (url: string) => ipcRenderer.invoke('minapp', url), - clearCache: () => ipcRenderer.invoke('app:clear-cache'), + getAppInfo: () => ipcRenderer.invoke(IpcChannel.App_Info), + reload: () => ipcRenderer.invoke(IpcChannel.App_Reload), + setProxy: (proxy: string) => ipcRenderer.invoke(IpcChannel.App_Proxy, proxy), + checkForUpdate: () => ipcRenderer.invoke(IpcChannel.App_CheckForUpdate), + showUpdateDialog: () => ipcRenderer.invoke(IpcChannel.App_ShowUpdateDialog), + setLanguage: (lang: string) => ipcRenderer.invoke(IpcChannel.App_SetLanguage, lang), + setLaunchOnBoot: (isActive: boolean) => ipcRenderer.invoke(IpcChannel.App_SetLaunchOnBoot, isActive), + setLaunchToTray: (isActive: boolean) => ipcRenderer.invoke(IpcChannel.App_SetLaunchToTray, isActive), + setTray: (isActive: boolean) => ipcRenderer.invoke(IpcChannel.App_SetTray, isActive), + setTrayOnClose: (isActive: boolean) => ipcRenderer.invoke(IpcChannel.App_SetTrayOnClose, isActive), + restartTray: () => ipcRenderer.invoke(IpcChannel.App_RestartTray), + setTheme: (theme: 'light' | 'dark') => ipcRenderer.invoke(IpcChannel.App_SetTheme, theme), + openWebsite: (url: string) => ipcRenderer.invoke(IpcChannel.Open_Website, url), + minApp: (url: string) => ipcRenderer.invoke(IpcChannel.Minapp, url), + clearCache: () => ipcRenderer.invoke(IpcChannel.App_ClearCache), system: { - getDeviceType: () => ipcRenderer.invoke('system:getDeviceType') + getDeviceType: () => ipcRenderer.invoke(IpcChannel.System_GetDeviceType) }, zip: { - compress: (text: string) => ipcRenderer.invoke('zip:compress', text), - decompress: (text: Buffer) => ipcRenderer.invoke('zip:decompress', text) + compress: (text: string) => ipcRenderer.invoke(IpcChannel.Zip_Compress, text), + decompress: (text: Buffer) => ipcRenderer.invoke(IpcChannel.Zip_Decompress, text) }, backup: { backup: (fileName: string, data: string, destinationPath?: string) => - ipcRenderer.invoke('backup:backup', fileName, data, destinationPath), - restore: (backupPath: string) => ipcRenderer.invoke('backup:restore', backupPath), + ipcRenderer.invoke(IpcChannel.Backup_Backup, fileName, data, destinationPath), + restore: (backupPath: string) => ipcRenderer.invoke(IpcChannel.Backup_Restore, backupPath), backupToWebdav: (data: string, webdavConfig: WebDavConfig) => - ipcRenderer.invoke('backup:backupToWebdav', data, webdavConfig), - restoreFromWebdav: (webdavConfig: WebDavConfig) => ipcRenderer.invoke('backup:restoreFromWebdav', webdavConfig), - listWebdavFiles: (webdavConfig: WebDavConfig) => ipcRenderer.invoke('backup:listWebdavFiles', webdavConfig), - checkConnection: (webdavConfig: WebDavConfig) => ipcRenderer.invoke('backup:checkConnection', webdavConfig), + ipcRenderer.invoke(IpcChannel.Backup_BackupToWebdav, data, webdavConfig), + restoreFromWebdav: (webdavConfig: WebDavConfig) => ipcRenderer.invoke(IpcChannel.Backup_RestoreFromWebdav, webdavConfig), + listWebdavFiles: (webdavConfig: WebDavConfig) => ipcRenderer.invoke(IpcChannel.Backup_ListWebdavFiles, webdavConfig), + checkConnection: (webdavConfig: WebDavConfig) => ipcRenderer.invoke(IpcChannel.Backup_CheckConnection, webdavConfig), createDirectory: (webdavConfig: WebDavConfig, path: string, options?: CreateDirectoryOptions) => - ipcRenderer.invoke('backup:createDirectory', webdavConfig, path, options) + ipcRenderer.invoke(IpcChannel.Backup_CreateDirectory, webdavConfig, path, options) }, file: { - select: (options?: OpenDialogOptions) => ipcRenderer.invoke('file:select', options), - upload: (filePath: string) => ipcRenderer.invoke('file:upload', filePath), - delete: (fileId: string) => ipcRenderer.invoke('file:delete', fileId), - read: (fileId: string) => ipcRenderer.invoke('file:read', fileId), - clear: () => ipcRenderer.invoke('file:clear'), - get: (filePath: string) => ipcRenderer.invoke('file:get', filePath), - create: (fileName: string) => ipcRenderer.invoke('file:create', fileName), - write: (filePath: string, data: Uint8Array | string) => ipcRenderer.invoke('file:write', filePath, data), - open: (options?: { decompress: boolean }) => ipcRenderer.invoke('file:open', options), - openPath: (path: string) => ipcRenderer.invoke('file:openPath', path), + select: (options?: OpenDialogOptions) => ipcRenderer.invoke(IpcChannel.File_Select, options), + upload: (filePath: string) => ipcRenderer.invoke(IpcChannel.File_Upload, filePath), + delete: (fileId: string) => ipcRenderer.invoke(IpcChannel.File_Delete, fileId), + read: (fileId: string) => ipcRenderer.invoke(IpcChannel.File_Read, fileId), + clear: () => ipcRenderer.invoke(IpcChannel.File_Clear), + get: (filePath: string) => ipcRenderer.invoke(IpcChannel.File_Get, filePath), + create: (fileName: string) => ipcRenderer.invoke(IpcChannel.File_Create, fileName), + write: (filePath: string, data: Uint8Array | string) => ipcRenderer.invoke(IpcChannel.File_Write, filePath, data), + open: (options?: { decompress: boolean }) => ipcRenderer.invoke(IpcChannel.File_Open, options), + openPath: (path: string) => ipcRenderer.invoke(IpcChannel.File_OpenPath, path), save: (path: string, content: string, options?: { compress: boolean }) => - ipcRenderer.invoke('file:save', path, content, options), - selectFolder: () => ipcRenderer.invoke('file:selectFolder'), - saveImage: (name: string, data: string) => ipcRenderer.invoke('file:saveImage', name, data), - base64Image: (fileId: string) => ipcRenderer.invoke('file:base64Image', fileId), - download: (url: string) => ipcRenderer.invoke('file:download', url), - copy: (fileId: string, destPath: string) => ipcRenderer.invoke('file:copy', fileId, destPath), - binaryFile: (fileId: string) => ipcRenderer.invoke('file:binaryFile', fileId) + ipcRenderer.invoke(IpcChannel.File_Save, path, content, options), + selectFolder: () => ipcRenderer.invoke(IpcChannel.File_SelectFolder), + saveImage: (name: string, data: string) => ipcRenderer.invoke(IpcChannel.File_SaveImage, name, data), + base64Image: (fileId: string) => ipcRenderer.invoke(IpcChannel.File_Base64Image, fileId), + download: (url: string) => ipcRenderer.invoke(IpcChannel.File_Download, url), + copy: (fileId: string, destPath: string) => ipcRenderer.invoke(IpcChannel.File_Copy, fileId, destPath), + binaryFile: (fileId: string) => ipcRenderer.invoke(IpcChannel.File_BinaryFile, fileId) }, fs: { - read: (path: string) => ipcRenderer.invoke('fs:read', path) + read: (path: string) => ipcRenderer.invoke(IpcChannel.Fs_Read, path) }, export: { - toWord: (markdown: string, fileName: string) => ipcRenderer.invoke('export:word', markdown, fileName) + toWord: (markdown: string, fileName: string) => ipcRenderer.invoke(IpcChannel.Export_Word, markdown, fileName) }, - openPath: (path: string) => ipcRenderer.invoke('open:path', path), + openPath: (path: string) => ipcRenderer.invoke(IpcChannel.Open_Path, path), shortcuts: { - update: (shortcuts: Shortcut[]) => ipcRenderer.invoke('shortcuts:update', shortcuts) + update: (shortcuts: Shortcut[]) => ipcRenderer.invoke(IpcChannel.Shortcuts_Update, shortcuts) }, knowledgeBase: { - create: (base: KnowledgeBaseParams) => ipcRenderer.invoke('knowledge-base:create', base), - reset: (base: KnowledgeBaseParams) => ipcRenderer.invoke('knowledge-base:reset', base), - delete: (id: string) => ipcRenderer.invoke('knowledge-base:delete', id), + create: (base: KnowledgeBaseParams) => ipcRenderer.invoke(IpcChannel.KnowledgeBase_Create, base), + reset: (base: KnowledgeBaseParams) => ipcRenderer.invoke(IpcChannel.KnowledgeBase_Reset, base), + delete: (id: string) => ipcRenderer.invoke(IpcChannel.KnowledgeBase_Delete, id), add: ({ base, item, @@ -82,71 +83,74 @@ const api = { base: KnowledgeBaseParams item: KnowledgeItem forceReload?: boolean - }) => ipcRenderer.invoke('knowledge-base:add', { base, item, forceReload }), + }) => ipcRenderer.invoke(IpcChannel.KnowledgeBase_Add, { base, item, forceReload }), remove: ({ uniqueId, uniqueIds, base }: { uniqueId: string; uniqueIds: string[]; base: KnowledgeBaseParams }) => - ipcRenderer.invoke('knowledge-base:remove', { uniqueId, uniqueIds, base }), + ipcRenderer.invoke(IpcChannel.KnowledgeBase_Remove, { uniqueId, uniqueIds, base }), search: ({ search, base }: { search: string; base: KnowledgeBaseParams }) => - ipcRenderer.invoke('knowledge-base:search', { search, base }), + ipcRenderer.invoke(IpcChannel.KnowledgeBase_Search, { search, base }), rerank: ({ search, base, results }: { search: string; base: KnowledgeBaseParams; results: ExtractChunkData[] }) => - ipcRenderer.invoke('knowledge-base:rerank', { search, base, results }) + ipcRenderer.invoke(IpcChannel.KnowledgeBase_Rerank, { search, base, results }) }, window: { - setMinimumSize: (width: number, height: number) => ipcRenderer.invoke('window:set-minimum-size', width, height), - resetMinimumSize: () => ipcRenderer.invoke('window:reset-minimum-size') + setMinimumSize: (width: number, height: number) => + ipcRenderer.invoke(IpcChannel.Windows_SetMinimumSize, width, height), + resetMinimumSize: () => ipcRenderer.invoke(IpcChannel.Windows_ResetMinimumSize) }, gemini: { - uploadFile: (file: FileType, apiKey: string) => ipcRenderer.invoke('gemini:upload-file', file, apiKey), - base64File: (file: FileType) => ipcRenderer.invoke('gemini:base64-file', file), - retrieveFile: (file: FileType, apiKey: string) => ipcRenderer.invoke('gemini:retrieve-file', file, apiKey), - listFiles: (apiKey: string) => ipcRenderer.invoke('gemini:list-files', apiKey), - deleteFile: (apiKey: string, fileId: string) => ipcRenderer.invoke('gemini:delete-file', apiKey, fileId) + uploadFile: (file: FileType, apiKey: string) => ipcRenderer.invoke(IpcChannel.Gemini_UploadFile, file, apiKey), + base64File: (file: FileType) => ipcRenderer.invoke(IpcChannel.Gemini_Base64File, file), + retrieveFile: (file: FileType, apiKey: string) => ipcRenderer.invoke(IpcChannel.Gemini_RetrieveFile, file, apiKey), + listFiles: (apiKey: string) => ipcRenderer.invoke(IpcChannel.Gemini_ListFiles, apiKey), + deleteFile: (apiKey: string, fileId: string) => ipcRenderer.invoke(IpcChannel.Gemini_DeleteFile, apiKey, fileId) }, selectionMenu: { - action: (action: string) => ipcRenderer.invoke('selection-menu:action', action) + action: (action: string) => ipcRenderer.invoke(IpcChannel.SelectionMenu_Action, action) }, config: { - set: (key: string, value: any) => ipcRenderer.invoke('config:set', key, value), - get: (key: string) => ipcRenderer.invoke('config:get', key) + set: (key: string, value: any) => ipcRenderer.invoke(IpcChannel.Config_Set, key, value), + get: (key: string) => ipcRenderer.invoke(IpcChannel.Config_Get, key) }, miniWindow: { - show: () => ipcRenderer.invoke('miniwindow:show'), - hide: () => ipcRenderer.invoke('miniwindow:hide'), - close: () => ipcRenderer.invoke('miniwindow:close'), - toggle: () => ipcRenderer.invoke('miniwindow:toggle'), - setPin: (isPinned: boolean) => ipcRenderer.invoke('miniwindow:set-pin', isPinned) + show: () => ipcRenderer.invoke(IpcChannel.MiniWindow_Show), + hide: () => ipcRenderer.invoke(IpcChannel.MiniWindow_Hide), + close: () => ipcRenderer.invoke(IpcChannel.MiniWindow_Close), + toggle: () => ipcRenderer.invoke(IpcChannel.MiniWindow_Toggle) + setPin: (isPinned: boolean) => ipcRenderer.invoke(IpcChannel.MiniWindow_SetPin, isPinned) }, aes: { - encrypt: (text: string, secretKey: string, iv: string) => ipcRenderer.invoke('aes:encrypt', text, secretKey, iv), + encrypt: (text: string, secretKey: string, iv: string) => + ipcRenderer.invoke(IpcChannel.Aes_Encrypt, text, secretKey, iv), decrypt: (encryptedData: string, iv: string, secretKey: string) => - ipcRenderer.invoke('aes:decrypt', encryptedData, iv, secretKey) + ipcRenderer.invoke(IpcChannel.Aes_Decrypt, encryptedData, iv, secretKey) }, mcp: { - removeServer: (server: MCPServer) => ipcRenderer.invoke('mcp:remove-server', server), - restartServer: (server: MCPServer) => ipcRenderer.invoke('mcp:restart-server', server), - stopServer: (server: MCPServer) => ipcRenderer.invoke('mcp:stop-server', server), - listTools: (server: MCPServer) => ipcRenderer.invoke('mcp:list-tools', server), + removeServer: (server: MCPServer) => ipcRenderer.invoke(IpcChannel.Mcp_RemoveServer, server), + restartServer: (server: MCPServer) => ipcRenderer.invoke(IpcChannel.Mcp_RestartServer, server), + stopServer: (server: MCPServer) => ipcRenderer.invoke(IpcChannel.Mcp_StopServer, server), + listTools: (server: MCPServer) => ipcRenderer.invoke(IpcChannel.Mcp_ListTools, server), callTool: ({ server, name, args }: { server: MCPServer; name: string; args: any }) => - ipcRenderer.invoke('mcp:call-tool', { server, name, args }), - getInstallInfo: () => ipcRenderer.invoke('mcp:get-install-info') + ipcRenderer.invoke(IpcChannel.Mcp_CallTool, { server, name, args }), + getInstallInfo: () => ipcRenderer.invoke(IpcChannel.Mcp_GetInstallInfo) }, shell: { openExternal: shell.openExternal }, copilot: { - getAuthMessage: (headers?: Record) => ipcRenderer.invoke('copilot:get-auth-message', headers), + getAuthMessage: (headers?: Record) => + ipcRenderer.invoke(IpcChannel.Copilot_GetAuthMessage, headers), getCopilotToken: (device_code: string, headers?: Record) => - ipcRenderer.invoke('copilot:get-copilot-token', device_code, headers), - saveCopilotToken: (access_token: string) => ipcRenderer.invoke('copilot:save-copilot-token', access_token), - getToken: (headers?: Record) => ipcRenderer.invoke('copilot:get-token', headers), - logout: () => ipcRenderer.invoke('copilot:logout'), - getUser: (token: string) => ipcRenderer.invoke('copilot:get-user', token) + ipcRenderer.invoke(IpcChannel.Copilot_GetCopilotToken, device_code, headers), + saveCopilotToken: (access_token: string) => ipcRenderer.invoke(IpcChannel.Copilot_SaveCopilotToken, access_token), + getToken: (headers?: Record) => ipcRenderer.invoke(IpcChannel.Copilot_GetToken, headers), + logout: () => ipcRenderer.invoke(IpcChannel.Copilot_Logout), + getUser: (token: string) => ipcRenderer.invoke(IpcChannel.Copilot_GetUser, token) }, // Binary related APIs - isBinaryExist: (name: string) => ipcRenderer.invoke('app:is-binary-exist', name), - getBinaryPath: (name: string) => ipcRenderer.invoke('app:get-binary-path', name), - installUVBinary: () => ipcRenderer.invoke('app:install-uv-binary'), - installBunBinary: () => ipcRenderer.invoke('app:install-bun-binary'), + isBinaryExist: (name: string) => ipcRenderer.invoke(IpcChannel.App_IsBinaryExist, name), + getBinaryPath: (name: string) => ipcRenderer.invoke(IpcChannel.App_GetBinaryPath, name), + installUVBinary: () => ipcRenderer.invoke(IpcChannel.App_InstallUvBinary), + installBunBinary: () => ipcRenderer.invoke(IpcChannel.App_InstallBunBinary) protocol: { onReceiveData: (callback: (data: { url: string; params: any }) => void) => { const listener = (_event: Electron.IpcRendererEvent, data: { url: string; params: any }) => { @@ -159,10 +163,10 @@ const api = { } }, nutstore: { - getSSOUrl: () => ipcRenderer.invoke('nutstore:get-sso-url'), - decryptToken: (token: string) => ipcRenderer.invoke('nutstore:decrypt-token', token), + getSSOUrl: () => ipcRenderer.invoke(IpcChannel.Nutstore_GetSsoUrl), + decryptToken: (token: string) => ipcRenderer.invoke(IpcChannel.Nutstore_DecryptToken, token), getDirectoryContents: (token: string, path: string) => - ipcRenderer.invoke('nutstore:get-directory-contents', token, path) + ipcRenderer.invoke(IpcChannel.Nutstore_GetDirectoryContents, token, path) } } @@ -174,9 +178,9 @@ if (process.contextIsolated) { contextBridge.exposeInMainWorld('electron', electronAPI) contextBridge.exposeInMainWorld('api', api) contextBridge.exposeInMainWorld('obsidian', { - getVaults: () => ipcRenderer.invoke('obsidian:get-vaults'), - getFolders: (vaultName: string) => ipcRenderer.invoke('obsidian:get-files', vaultName), - getFiles: (vaultName: string) => ipcRenderer.invoke('obsidian:get-files', vaultName) + getVaults: () => ipcRenderer.invoke(IpcChannel.Obsidian_GetVaults), + getFolders: (vaultName: string) => ipcRenderer.invoke(IpcChannel.Obsidian_GetFiles, vaultName), + getFiles: (vaultName: string) => ipcRenderer.invoke(IpcChannel.Obsidian_GetFiles, vaultName) }) } catch (error) { console.error(error) diff --git a/src/renderer/src/components/Popups/BackupPopup.tsx b/src/renderer/src/components/Popups/BackupPopup.tsx index 07ca7991..813e27a1 100644 --- a/src/renderer/src/components/Popups/BackupPopup.tsx +++ b/src/renderer/src/components/Popups/BackupPopup.tsx @@ -4,6 +4,7 @@ import { useEffect, useState } from 'react' import { useTranslation } from 'react-i18next' import { TopView } from '../TopView' +import { IpcChannel } from '@shared/IpcChannel' interface Props { resolve: (data: any) => void @@ -21,7 +22,7 @@ const PopupContainer: React.FC = ({ resolve }) => { const { t } = useTranslation() useEffect(() => { - const removeListener = window.electron.ipcRenderer.on('backup-progress', (_, data: ProgressData) => { + const removeListener = window.electron.ipcRenderer.on(IpcChannel.BackupProgress, (_, data: ProgressData) => { setProgressData(data) }) diff --git a/src/renderer/src/components/Popups/RestorePopup.tsx b/src/renderer/src/components/Popups/RestorePopup.tsx index 2e6d166c..ee9ef850 100644 --- a/src/renderer/src/components/Popups/RestorePopup.tsx +++ b/src/renderer/src/components/Popups/RestorePopup.tsx @@ -4,6 +4,7 @@ import { useEffect, useState } from 'react' import { useTranslation } from 'react-i18next' import { TopView } from '../TopView' +import { IpcChannel } from '@shared/IpcChannel' interface Props { resolve: (data: any) => void @@ -21,7 +22,7 @@ const PopupContainer: React.FC = ({ resolve }) => { const { t } = useTranslation() useEffect(() => { - const removeListener = window.electron.ipcRenderer.on('restore-progress', (_, data: ProgressData) => { + const removeListener = window.electron.ipcRenderer.on(IpcChannel.RestoreProgress, (_, data: ProgressData) => { setProgressData(data) }) diff --git a/src/renderer/src/context/ThemeProvider.tsx b/src/renderer/src/context/ThemeProvider.tsx index 24c6bce1..14c77de5 100644 --- a/src/renderer/src/context/ThemeProvider.tsx +++ b/src/renderer/src/context/ThemeProvider.tsx @@ -2,6 +2,7 @@ import { isMac } from '@renderer/config/constant' import { useSettings } from '@renderer/hooks/useSettings' import { ThemeMode } from '@renderer/types' import React, { createContext, PropsWithChildren, use, useEffect, useState } from 'react' +import { IpcChannel } from '@shared/IpcChannel' interface ThemeContextType { theme: ThemeMode @@ -49,7 +50,7 @@ export const ThemeProvider: React.FC = ({ children, defaultT document.body.setAttribute('os', isMac ? 'mac' : 'windows') // listen theme change from main process from other windows - const themeChangeListenerRemover = window.electron.ipcRenderer.on('theme:change', (_, newTheme) => { + const themeChangeListenerRemover = window.electron.ipcRenderer.on(IpcChannel.ThemeChange, (_, newTheme) => { setTheme(newTheme) }) return () => { diff --git a/src/renderer/src/hooks/useAppInit.ts b/src/renderer/src/hooks/useAppInit.ts index 752ed071..112bd11d 100644 --- a/src/renderer/src/hooks/useAppInit.ts +++ b/src/renderer/src/hooks/useAppInit.ts @@ -15,6 +15,8 @@ import { useRuntime } from './useRuntime' import { useSettings } from './useSettings' import useUpdateHandler from './useUpdateHandler' +import { defaultLanguage } from '@shared/config/constant' + export function useAppInit() { const dispatch = useAppDispatch() const { proxyUrl, language, windowStyle, autoCheckUpdate, proxyMode, customCss } = useSettings() @@ -53,7 +55,7 @@ export function useAppInit() { }, [proxyUrl, proxyMode]) useEffect(() => { - i18n.changeLanguage(language || navigator.language || 'en-US') + i18n.changeLanguage(language || navigator.language || defaultLanguage) }, [language]) useEffect(() => { diff --git a/src/renderer/src/hooks/useFullScreenNotice.ts b/src/renderer/src/hooks/useFullScreenNotice.ts index e81f9fca..ea62e0f2 100644 --- a/src/renderer/src/hooks/useFullScreenNotice.ts +++ b/src/renderer/src/hooks/useFullScreenNotice.ts @@ -1,12 +1,13 @@ import { isWindows } from '@renderer/config/constant' import { useEffect } from 'react' import { useTranslation } from 'react-i18next' +import { IpcChannel } from '@shared/IpcChannel' export function useFullScreenNotice() { const { t } = useTranslation() useEffect(() => { - const cleanup = window.electron.ipcRenderer.on('fullscreen-status-changed', (_, isFullscreen) => { + const cleanup = window.electron.ipcRenderer.on(IpcChannel.FullscreenStatusChanged, (_, isFullscreen) => { if (isWindows && isFullscreen) { window.message.info({ content: t('common.fullscreen'), diff --git a/src/renderer/src/hooks/useKnowledge.ts b/src/renderer/src/hooks/useKnowledge.ts index 628a51ec..4eed0093 100644 --- a/src/renderer/src/hooks/useKnowledge.ts +++ b/src/renderer/src/hooks/useKnowledge.ts @@ -27,6 +27,7 @@ import { v4 as uuidv4 } from 'uuid' import { useAgents } from './useAgents' import { useAssistants } from './useAssistant' +import { IpcChannel } from '@shared/IpcChannel' export const useKnowledge = (baseId: string) => { const dispatch = useDispatch() @@ -207,7 +208,7 @@ export const useKnowledge = (baseId: string) => { } const cleanup = window.electron.ipcRenderer.on( - 'directory-processing-percent', + IpcChannel.DirectoryProcessingPercent, (_, { itemId: id, percent }: { itemId: string; percent: number }) => { if (itemId === id) { setPercent(percent) diff --git a/src/renderer/src/hooks/useMCPServers.ts b/src/renderer/src/hooks/useMCPServers.ts index 65bc729d..6cf16a29 100644 --- a/src/renderer/src/hooks/useMCPServers.ts +++ b/src/renderer/src/hooks/useMCPServers.ts @@ -1,11 +1,12 @@ import store, { useAppDispatch, useAppSelector } from '@renderer/store' import { addMCPServer, deleteMCPServer, setMCPServers, updateMCPServer } from '@renderer/store/mcp' import { MCPServer } from '@renderer/types' +import { IpcChannel } from '@shared/IpcChannel' const ipcRenderer = window.electron.ipcRenderer // Listen for server changes from main process -ipcRenderer.on('mcp:servers-changed', (_event, servers) => { +ipcRenderer.on(IpcChannel.Mcp_ServersChanged, (_event, servers) => { store.dispatch(setMCPServers(servers)) }) diff --git a/src/renderer/src/hooks/useUpdateHandler.ts b/src/renderer/src/hooks/useUpdateHandler.ts index a8fe07f8..5dd42e03 100644 --- a/src/renderer/src/hooks/useUpdateHandler.ts +++ b/src/renderer/src/hooks/useUpdateHandler.ts @@ -3,6 +3,7 @@ import { setUpdateState } from '@renderer/store/runtime' import type { ProgressInfo, UpdateInfo } from 'builder-util-runtime' import { useEffect } from 'react' import { useTranslation } from 'react-i18next' +import { IpcChannel } from '@shared/IpcChannel' export default function useUpdateHandler() { const dispatch = useAppDispatch() @@ -14,13 +15,13 @@ export default function useUpdateHandler() { const ipcRenderer = window.electron.ipcRenderer const removers = [ - ipcRenderer.on('update-not-available', () => { + ipcRenderer.on(IpcChannel.UpdateNotAvailable, () => { dispatch(setUpdateState({ checking: false })) if (window.location.hash.includes('settings/about')) { window.message.success(t('settings.about.updateNotAvailable')) } }), - ipcRenderer.on('update-available', (_, releaseInfo: UpdateInfo) => { + ipcRenderer.on(IpcChannel.UpdateAvailable, (_, releaseInfo: UpdateInfo) => { dispatch( setUpdateState({ checking: false, @@ -30,7 +31,7 @@ export default function useUpdateHandler() { }) ) }), - ipcRenderer.on('download-update', () => { + ipcRenderer.on(IpcChannel.DownloadUpdate, () => { dispatch( setUpdateState({ checking: false, @@ -38,7 +39,7 @@ export default function useUpdateHandler() { }) ) }), - ipcRenderer.on('download-progress', (_, progress: ProgressInfo) => { + ipcRenderer.on(IpcChannel.DownloadProgress, (_, progress: ProgressInfo) => { dispatch( setUpdateState({ downloading: progress.percent < 100, @@ -46,7 +47,7 @@ export default function useUpdateHandler() { }) ) }), - ipcRenderer.on('update-downloaded', (_, releaseInfo: UpdateInfo) => { + ipcRenderer.on(IpcChannel.UpdateDownloaded, (_, releaseInfo: UpdateInfo) => { dispatch( setUpdateState({ downloading: false, @@ -55,7 +56,7 @@ export default function useUpdateHandler() { }) ) }), - ipcRenderer.on('update-error', (_, error) => { + ipcRenderer.on(IpcChannel.UpdateError, (_, error) => { dispatch( setUpdateState({ checking: false, diff --git a/src/renderer/src/i18n/index.ts b/src/renderer/src/i18n/index.ts index ff4dda95..3b6dc7a3 100644 --- a/src/renderer/src/i18n/index.ts +++ b/src/renderer/src/i18n/index.ts @@ -13,6 +13,8 @@ import esES from './translate/es-es.json' import frFR from './translate/fr-fr.json' import ptPT from './translate/pt-pt.json' +import { defaultLanguage } from '@shared/config/constant' + const resources = { 'el-GR': elGR, 'en-US': enUS, @@ -26,7 +28,7 @@ const resources = { } export const getLanguage = () => { - return localStorage.getItem('language') || navigator.language || 'en-US' + return localStorage.getItem('language') || navigator.language || defaultLanguage } export const getLanguageCode = () => { @@ -36,7 +38,7 @@ export const getLanguageCode = () => { i18n.use(initReactI18next).init({ resources, lng: getLanguage(), - fallbackLng: 'en-US', + fallbackLng: defaultLanguage, interpolation: { escapeValue: false } diff --git a/src/renderer/src/pages/files/GeminiFiles.tsx b/src/renderer/src/pages/files/GeminiFiles.tsx index a664856b..3e2739db 100644 --- a/src/renderer/src/pages/files/GeminiFiles.tsx +++ b/src/renderer/src/pages/files/GeminiFiles.tsx @@ -8,6 +8,7 @@ import { FC, useCallback, useEffect, useState } from 'react' import styled from 'styled-components' import FileItem from './FileItem' +import { MB } from '@shared/config/constant' interface GeminiFilesProps { id: string @@ -60,7 +61,7 @@ const GeminiFiles: FC = ({ id }) => { fileInfo={{ name: file.displayName, ext: `.${file.name.split('.').pop()}`, - extra: `${dayjs(file.createTime).format('MM-DD HH:mm')} · ${(parseInt(file.sizeBytes) / 1024 / 1024).toFixed(2)} MB`, + extra: `${dayjs(file.createTime).format('MM-DD HH:mm')} · ${(parseInt(file.sizeBytes) / MB).toFixed(2)} MB`, actions: ( { const { @@ -112,7 +113,7 @@ const GeneralSettings: FC = () => { {t('common.language')} - {languagesOptions.map((lang) => ( diff --git a/src/renderer/src/providers/AiProvider/GeminiProvider.ts b/src/renderer/src/providers/AiProvider/GeminiProvider.ts index d9ac5139..5ec4bf67 100644 --- a/src/renderer/src/providers/AiProvider/GeminiProvider.ts +++ b/src/renderer/src/providers/AiProvider/GeminiProvider.ts @@ -44,6 +44,7 @@ import OpenAI from 'openai' import { ChunkCallbackData, CompletionsParams } from '.' import BaseProvider from './BaseProvider' +import { MB } from '@shared/config/constant' export default class GeminiProvider extends BaseProvider { private sdk: GoogleGenerativeAI @@ -70,7 +71,7 @@ export default class GeminiProvider extends BaseProvider { * @returns The part */ private async handlePdfFile(file: FileType): Promise { - const smallFileSize = 20 * 1024 * 1024 + const smallFileSize = 20 * MB const isSmallFile = file.size < smallFileSize if (isSmallFile) { diff --git a/src/renderer/src/store/llm.ts b/src/renderer/src/store/llm.ts index 00da25f4..b50f3f11 100644 --- a/src/renderer/src/store/llm.ts +++ b/src/renderer/src/store/llm.ts @@ -3,6 +3,7 @@ import { isLocalAi } from '@renderer/config/env' import { SYSTEM_MODELS } from '@renderer/config/models' import { Model, Provider } from '@renderer/types' import { uniqBy } from 'lodash' +import { IpcChannel } from '@shared/IpcChannel' type LlmSettings = { ollama: { @@ -572,7 +573,7 @@ const settingsSlice = createSlice({ }, setDefaultModel: (state, action: PayloadAction<{ model: Model }>) => { state.defaultModel = action.payload.model - window.electron.ipcRenderer.send('miniwindow-reload') + window.electron.ipcRenderer.send(IpcChannel.MiniWindowReload) }, setTopicNamingModel: (state, action: PayloadAction<{ model: Model }>) => { state.topicNamingModel = action.payload.model diff --git a/src/renderer/src/store/settings.ts b/src/renderer/src/store/settings.ts index 2fc29bf0..97bd9289 100644 --- a/src/renderer/src/store/settings.ts +++ b/src/renderer/src/store/settings.ts @@ -1,6 +1,7 @@ import { createSlice, PayloadAction } from '@reduxjs/toolkit' import { TRANSLATE_PROMPT } from '@renderer/config/prompts' import { CodeStyleVarious, LanguageVarious, ThemeMode, TranslateLanguageVarious } from '@renderer/types' +import { IpcChannel } from '@shared/IpcChannel' import { WebDAVSyncState } from './backup' @@ -208,7 +209,7 @@ const settingsSlice = createSlice({ }, setLanguage: (state, action: PayloadAction) => { state.language = action.payload - window.electron.ipcRenderer.send('miniwindow-reload') + window.electron.ipcRenderer.send(IpcChannel.MiniWindowReload) }, setTargetLanguage: (state, action: PayloadAction) => { state.targetLanguage = action.payload diff --git a/src/renderer/src/utils/index.ts b/src/renderer/src/utils/index.ts index 10679d92..0584f8a9 100644 --- a/src/renderer/src/utils/index.ts +++ b/src/renderer/src/utils/index.ts @@ -7,6 +7,7 @@ import * as htmlToImage from 'html-to-image' import { v4 as uuidv4 } from 'uuid' import { classNames } from './style' +import { KB, MB } from '@shared/config/constant' export const runAsyncFunction = async (fn: () => void) => { await fn() @@ -421,15 +422,15 @@ export function hasPath(url: string): boolean { } export function formatFileSize(size: number) { - if (size > 1024 * 1024) { - return (size / 1024 / 1024).toFixed(1) + ' MB' + if (size > MB) { + return (size / MB).toFixed(1) + ' MB' } - if (size > 1024) { - return (size / 1024).toFixed(0) + ' KB' + if (size > KB) { + return (size / KB).toFixed(0) + ' KB' } - return (size / 1024).toFixed(2) + ' KB' + return (size / KB).toFixed(2) + ' KB' } export function sortByEnglishFirst(a: string, b: string) { diff --git a/src/renderer/src/windows/mini/home/HomeWindow.tsx b/src/renderer/src/windows/mini/home/HomeWindow.tsx index 74c42f7b..1904c438 100644 --- a/src/renderer/src/windows/mini/home/HomeWindow.tsx +++ b/src/renderer/src/windows/mini/home/HomeWindow.tsx @@ -18,6 +18,9 @@ import ClipboardPreview from './components/ClipboardPreview' import FeatureMenus, { FeatureMenusRef } from './components/FeatureMenus' import Footer from './components/Footer' import InputBar from './components/InputBar' +import { IpcChannel } from '@shared/IpcChannel' + +import { defaultLanguage } from '@shared/config/constant' const HomeWindow: FC = () => { const [route, setRoute] = useState<'home' | 'chat' | 'translate' | 'summary' | 'explanation'>('home') @@ -68,7 +71,7 @@ const HomeWindow: FC = () => { }, [readClipboard]) useEffect(() => { - i18n.changeLanguage(language || navigator.language || 'en-US') + i18n.changeLanguage(language || navigator.language || defaultLanguage) }, [language]) const onCloseWindow = () => window.api.miniWindow.hide() @@ -181,16 +184,16 @@ const HomeWindow: FC = () => { }) useEffect(() => { - window.electron.ipcRenderer.on('show-mini-window', onWindowShow) - window.electron.ipcRenderer.on('selection-action', (_, { action, selectedText }) => { + window.electron.ipcRenderer.on(IpcChannel.ShowMiniWindow, onWindowShow) + window.electron.ipcRenderer.on(IpcChannel.SelectionAction, (_, { action, selectedText }) => { selectedText && setSelectedText(selectedText) action && setRoute(action) action === 'chat' && onSendMessage() }) return () => { - window.electron.ipcRenderer.removeAllListeners('show-mini-window') - window.electron.ipcRenderer.removeAllListeners('selection-action') + window.electron.ipcRenderer.removeAllListeners(IpcChannel.ShowMiniWindow) + window.electron.ipcRenderer.removeAllListeners(IpcChannel.SelectionAction) } }, [onWindowShow, onSendMessage, setRoute])