mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2025-12-25 03:10:08 +08:00
321 lines
12 KiB
TypeScript
321 lines
12 KiB
TypeScript
import fs from 'node:fs'
|
|
|
|
import { isMac, isWin } from '@main/constant'
|
|
import { getBinaryPath, isBinaryExists, runInstallScript } from '@main/utils/process'
|
|
import { MCPServer, Shortcut, ThemeMode } from '@types'
|
|
import { BrowserWindow, ipcMain, session, shell } from 'electron'
|
|
import log from 'electron-log'
|
|
|
|
import { titleBarOverlayDark, titleBarOverlayLight } from './config'
|
|
import AppUpdater from './services/AppUpdater'
|
|
import BackupManager from './services/BackupManager'
|
|
import { configManager } from './services/ConfigManager'
|
|
import CopilotService from './services/CopilotService'
|
|
import { ExportService } from './services/ExportService'
|
|
import FileService from './services/FileService'
|
|
import FileStorage from './services/FileStorage'
|
|
import { GeminiService } from './services/GeminiService'
|
|
import KnowledgeService from './services/KnowledgeService'
|
|
import MCPService from './services/MCPService'
|
|
import ObsidianVaultService from './services/ObsidianVaultService'
|
|
import * as NutstoreService from './services/NutstoreService'
|
|
import { ProxyConfig, proxyManager } from './services/ProxyManager'
|
|
import { registerShortcuts, unregisterAllShortcuts } from './services/ShortcutService'
|
|
import { TrayService } from './services/TrayService'
|
|
import { windowService } from './services/WindowService'
|
|
import { getResourcePath } from './utils'
|
|
import { decrypt, encrypt } from './utils/aes'
|
|
import { getFilesDir } from './utils/file'
|
|
import { compress, decompress } from './utils/zip'
|
|
|
|
const fileManager = new FileStorage()
|
|
const backupManager = new BackupManager()
|
|
const exportService = new ExportService(fileManager)
|
|
const mcpService = new MCPService()
|
|
const obsidianVaultService = new ObsidianVaultService()
|
|
|
|
export function registerIpc(mainWindow: BrowserWindow, app: Electron.App) {
|
|
const appUpdater = new AppUpdater(mainWindow)
|
|
|
|
ipcMain.handle('app:info', () => ({
|
|
version: app.getVersion(),
|
|
isPackaged: app.isPackaged,
|
|
appPath: app.getAppPath(),
|
|
filesPath: getFilesDir(),
|
|
appDataPath: app.getPath('userData'),
|
|
resourcesPath: getResourcePath(),
|
|
logsPath: log.transports.file.getFile().path
|
|
}))
|
|
|
|
ipcMain.handle('app:proxy', async (_, proxy: string) => {
|
|
let proxyConfig: ProxyConfig
|
|
|
|
if (proxy === 'system') {
|
|
proxyConfig = { mode: 'system' }
|
|
} else if (proxy) {
|
|
proxyConfig = { mode: 'custom', url: proxy }
|
|
} else {
|
|
proxyConfig = { mode: 'none' }
|
|
}
|
|
|
|
await proxyManager.configureProxy(proxyConfig)
|
|
})
|
|
|
|
ipcMain.handle('app:reload', () => mainWindow.reload())
|
|
ipcMain.handle('open:website', (_, url: string) => shell.openExternal(url))
|
|
|
|
// Update
|
|
ipcMain.handle('app:show-update-dialog', () => appUpdater.showUpdateDialog(mainWindow))
|
|
|
|
// language
|
|
ipcMain.handle('app:set-language', (_, language) => {
|
|
configManager.setLanguage(language)
|
|
})
|
|
|
|
// launch on boot
|
|
ipcMain.handle('app:set-launch-on-boot', (_, openAtLogin: boolean) => {
|
|
// Set login item settings for windows and mac
|
|
// linux is not supported because it requires more file operations
|
|
if (isWin || isMac) {
|
|
app.setLoginItemSettings({ openAtLogin })
|
|
}
|
|
})
|
|
|
|
// launch to tray
|
|
ipcMain.handle('app:set-launch-to-tray', (_, isActive: boolean) => {
|
|
configManager.setLaunchToTray(isActive)
|
|
})
|
|
|
|
// tray
|
|
ipcMain.handle('app:set-tray', (_, isActive: boolean) => {
|
|
configManager.setTray(isActive)
|
|
})
|
|
|
|
// to tray on close
|
|
ipcMain.handle('app:set-tray-on-close', (_, isActive: boolean) => {
|
|
configManager.setTrayOnClose(isActive)
|
|
})
|
|
|
|
ipcMain.handle('app:restart-tray', () => TrayService.getInstance().restartTray())
|
|
|
|
ipcMain.handle('config:set', (_, key: string, value: any) => {
|
|
configManager.set(key, value)
|
|
})
|
|
|
|
ipcMain.handle('config:get', (_, key: string) => {
|
|
return configManager.get(key)
|
|
})
|
|
|
|
// theme
|
|
ipcMain.handle('app:set-theme', (event, theme: ThemeMode) => {
|
|
if (theme === configManager.getTheme()) return
|
|
|
|
configManager.setTheme(theme)
|
|
|
|
// should sync theme change to all windows
|
|
const senderWindowId = event.sender.id
|
|
const windows = BrowserWindow.getAllWindows()
|
|
// 向其他窗口广播主题变化
|
|
windows.forEach((win) => {
|
|
if (win.webContents.id !== senderWindowId) {
|
|
win.webContents.send('theme:change', theme)
|
|
}
|
|
})
|
|
|
|
mainWindow?.setTitleBarOverlay &&
|
|
mainWindow.setTitleBarOverlay(theme === 'dark' ? titleBarOverlayDark : titleBarOverlayLight)
|
|
})
|
|
|
|
// clear cache
|
|
ipcMain.handle('app:clear-cache', async () => {
|
|
const sessions = [session.defaultSession, session.fromPartition('persist:webview')]
|
|
|
|
try {
|
|
await Promise.all(
|
|
sessions.map(async (session) => {
|
|
await session.clearCache()
|
|
await session.clearStorageData({
|
|
storages: ['cookies', 'filesystem', 'shadercache', 'websql', 'serviceworkers', 'cachestorage']
|
|
})
|
|
})
|
|
)
|
|
await fileManager.clearTemp()
|
|
await fs.writeFileSync(log.transports.file.getFile().path, '')
|
|
return { success: true }
|
|
} catch (error: any) {
|
|
log.error('Failed to clear cache:', error)
|
|
return { success: false, error: error.message }
|
|
}
|
|
})
|
|
|
|
// check for update
|
|
ipcMain.handle('app:check-for-update', async () => {
|
|
const update = await appUpdater.autoUpdater.checkForUpdates()
|
|
return {
|
|
currentVersion: appUpdater.autoUpdater.currentVersion,
|
|
updateInfo: update?.updateInfo
|
|
}
|
|
})
|
|
|
|
// zip
|
|
ipcMain.handle('zip:compress', (_, text: string) => compress(text))
|
|
ipcMain.handle('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)
|
|
|
|
// 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)
|
|
|
|
// fs
|
|
ipcMain.handle('fs:read', FileService.readFile)
|
|
|
|
// minapp
|
|
ipcMain.handle('minapp', (_, args) => {
|
|
windowService.createMinappWindow({
|
|
url: args.url,
|
|
parent: mainWindow,
|
|
windowOptions: {
|
|
...mainWindow.getBounds(),
|
|
...args.windowOptions
|
|
}
|
|
})
|
|
})
|
|
|
|
// export
|
|
ipcMain.handle('export:word', exportService.exportToWord)
|
|
|
|
// open path
|
|
ipcMain.handle('open:path', async (_, path: string) => {
|
|
await shell.openPath(path)
|
|
})
|
|
|
|
// shortcuts
|
|
ipcMain.handle('shortcuts:update', (_, shortcuts: Shortcut[]) => {
|
|
configManager.setShortcuts(shortcuts)
|
|
// Refresh shortcuts registration
|
|
if (mainWindow) {
|
|
unregisterAllShortcuts()
|
|
registerShortcuts(mainWindow)
|
|
}
|
|
})
|
|
|
|
// 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)
|
|
|
|
// window
|
|
ipcMain.handle('window:set-minimum-size', (_, width: number, height: number) => {
|
|
mainWindow?.setMinimumSize(width, height)
|
|
})
|
|
|
|
ipcMain.handle('window:reset-minimum-size', () => {
|
|
mainWindow?.setMinimumSize(1080, 600)
|
|
const [width, height] = mainWindow?.getSize() ?? [1080, 600]
|
|
if (width < 1080) {
|
|
mainWindow?.setSize(1080, height)
|
|
}
|
|
})
|
|
|
|
// 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)
|
|
|
|
// 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())
|
|
|
|
// 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) =>
|
|
decrypt(encryptedData, iv, secretKey)
|
|
)
|
|
|
|
// Register MCP handlers
|
|
ipcMain.on('mcp:servers-from-renderer', (_, servers) => mcpService.setServers(servers))
|
|
ipcMain.handle('mcp:list-servers', async () => mcpService.listAvailableServices())
|
|
ipcMain.handle('mcp:add-server', async (_, server: MCPServer) => mcpService.addServer(server))
|
|
ipcMain.handle('mcp:update-server', async (_, server: MCPServer) => mcpService.updateServer(server))
|
|
ipcMain.handle('mcp:delete-server', async (_, serverName: string) => mcpService.deleteServer(serverName))
|
|
ipcMain.handle('mcp:set-server-active', async (_, { name, isActive }) =>
|
|
mcpService.setServerActive({ name, isActive })
|
|
)
|
|
|
|
// According to preload, this should take no parameters, but our implementation accepts
|
|
// an optional serverName for better flexibility
|
|
ipcMain.handle('mcp:list-tools', async (_, serverName?: string) => mcpService.listTools(serverName))
|
|
ipcMain.handle('mcp:call-tool', async (_, params: { client: string; name: string; args: any }) =>
|
|
mcpService.callTool(params)
|
|
)
|
|
|
|
ipcMain.handle('mcp:cleanup', async () => mcpService.cleanup())
|
|
|
|
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'))
|
|
|
|
// Listen for changes in MCP servers and notify renderer
|
|
mcpService.on('servers-updated', (servers) => {
|
|
mainWindow?.webContents.send('mcp:servers-updated', servers)
|
|
})
|
|
|
|
app.on('before-quit', () => mcpService.cleanup())
|
|
|
|
//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)
|
|
|
|
// Obsidian service
|
|
ipcMain.handle('obsidian:get-vaults', () => {
|
|
return obsidianVaultService.getVaults()
|
|
})
|
|
|
|
ipcMain.handle('obsidian:get-files', (_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) =>
|
|
NutstoreService.getDirectoryContents(token, path)
|
|
)
|
|
}
|