import { app, BrowserWindow, ipcMain } from 'electron' import { CONFIG_KEYS } from '@lib/constants' import { setupMainWindow } from './wins'; import started from 'electron-squirrel-startup' import configManager from '@electron/service/config-service' import { runTaskOperationService } from '@electron/process/runTaskOperationService' import { initScriptStoreService } from '@electron/service/script-store-service' import log from 'electron-log'; import 'bytenode'; // Ensure bytenode is bundled/externalized correctly import { appUpdater } from '@electron/service/updater'; import axios from 'axios'; import { providerApiService, onProviderChange } from '@electron/service/provider-api-service'; import { gatewayManager } from '@electron/gateway/manager'; // 初始化 updater,确保在 app ready 之前或者之中注册好 IPC appUpdater.init(); // 注册 hostapi:fetch IPC 代理 // 模型管理相关接口在本地处理(对齐 ClawX),其余接口代理到远端后端 const HOST_API_BASE_URL = process.env.VITE_SERVICE_URL || 'http://8.138.234.141/ingress'; async function handleLocalProviderApi(path: string, method: string, body: any) { const parsedBody = typeof body === 'string' && body ? JSON.parse(body) : body; if (path === '/api/provider-vendors' && method === 'GET') { return { success: true, ok: true, json: providerApiService.getVendors(), data: providerApiService.getVendors() }; } if (path === '/api/provider-accounts' && method === 'GET') { return { success: true, ok: true, json: providerApiService.getAccounts(), data: providerApiService.getAccounts() }; } if (path === '/api/providers' && method === 'GET') { return { success: true, ok: true, json: providerApiService.getProviders(), data: providerApiService.getProviders() }; } if (path === '/api/provider-accounts/default' && method === 'GET') { return { success: true, ok: true, json: providerApiService.getDefault(), data: providerApiService.getDefault() }; } if (path === '/api/provider-accounts' && method === 'POST') { const result = providerApiService.createAccount(parsedBody || {}); return { success: true, ok: true, json: result, data: result }; } if (path === '/api/provider-accounts/default' && method === 'PUT') { const result = providerApiService.setDefault(parsedBody || {}); return { success: result.success, ok: result.success, json: result, data: result }; } if (path.startsWith('/api/provider-accounts/') && method === 'PUT') { const id = decodeURIComponent(path.replace('/api/provider-accounts/', '')); const result = providerApiService.updateAccount(id, parsedBody || {}); return { success: result.success, ok: result.success, json: result, data: result }; } if (path.startsWith('/api/provider-accounts/') && method === 'DELETE') { const id = decodeURIComponent(path.replace('/api/provider-accounts/', '')); const result = providerApiService.deleteAccount(id); return { success: result.success, ok: result.success, json: result, data: result }; } if (path === '/api/providers/default' && method === 'PUT') { const result = providerApiService.setDefault({ accountId: parsedBody?.providerId }); return { success: result.success, ok: result.success, json: result, data: result }; } if (path.startsWith('/api/providers/') && path.endsWith('/api-key') && method === 'GET') { const id = decodeURIComponent(path.replace('/api/providers/', '').replace('/api-key', '')); const result = providerApiService.getApiKey(id); return { success: true, ok: true, json: result, data: result }; } if (path.startsWith('/api/providers/') && method === 'PUT') { // Provider updates are mapped to account updates for local storage const id = decodeURIComponent(path.replace('/api/providers/', '')); const result = providerApiService.updateAccount(id, parsedBody || {}); return { success: result.success, ok: result.success, json: result, data: result }; } if (path.startsWith('/api/providers/') && method === 'DELETE') { const [rawId, query] = path.replace('/api/providers/', '').split('?'); const id = decodeURIComponent(rawId); if (query && query.includes('apiKeyOnly=1')) { const result = providerApiService.deleteApiKey(id); return { success: result.success, ok: result.success, json: result, data: result }; } const result = providerApiService.deleteAccount(id); return { success: result.success, ok: result.success, json: result, data: result }; } if (path === '/api/providers/validate' && method === 'POST') { const result = await providerApiService.validateApiKey(parsedBody || {}); return { success: true, ok: true, json: result, data: result }; } if (path === '/api/usage/recent-token-history' && method === 'GET') { return { success: true, ok: true, json: providerApiService.getUsageHistory(), data: providerApiService.getUsageHistory() }; } return null; } ipcMain.handle('hostapi:fetch', async (_event, { path, method, headers, body }) => { // 1. 优先本地处理模型管理接口 const localResult = await handleLocalProviderApi(path, method || 'GET', body); if (localResult) return localResult; // 2. 其余接口代理到远端后端 const url = `${HOST_API_BASE_URL}${path}`; try { const response = await axios({ url, method: method || 'GET', headers: { 'Content-Type': 'application/json', ...headers, }, data: body ?? undefined, timeout: 30000, }); return { success: true, ok: true, json: response.data, data: response.data, }; } catch (error: any) { if (error.response) { return { success: false, ok: false, status: error.response.status, error: error.response.data?.message || error.message, text: error.response.statusText, data: error.response.data, }; } return { success: false, ok: false, error: error.message || 'Unknown error', }; } }); // Gateway RPC IPC handler ipcMain.handle('gateway:rpc', async (_event, method: string, params: any) => { return gatewayManager.rpc(method, params); }); // import logManager from '@electron/service/logger' // Handle creating/removing shortcuts on Windows when installing/uninstalling. if (started) { app.quit(); } // process.on('uncaughtException', (err) => { // logManager.error('uncaughtException', err); // }); // process.on('unhandledRejection', (reason, promise) => { // logManager.error('unhandledRejection', reason, promise); // }); app.whenReady().then(() => { gatewayManager.init(); onProviderChange(() => { gatewayManager.reloadProviders(); }); setupMainWindow(); // 初始化脚本存储服务 initScriptStoreService() // 开启任务操作子进程 runTaskOperationService() // 开启subagent子进程 }); // Quit when all windows are closed, except on macOS. There, it's common // for applications and their menu bar to stay active until the user quits // explicitly with Cmd + Q. app.on('window-all-closed', () => { if (process.platform !== 'darwin' && !configManager.get(CONFIG_KEYS.MINIMIZE_TO_TRAY)) { log.info('app closing due to all windows being closed'); app.quit(); } }); // On OS X it's common to re-create a window in the app when the // dock icon is clicked and there are no other windows open. app.on('activate', () => { if (BrowserWindow.getAllWindows().length === 0) { setupMainWindow(); } });