- Created a new test file `channels.test.ts` to cover utilities related to channel configurations and targets. - Implemented tests for normalizing and grouping selected channels by type, as well as building channel targets from account data and cron history. - Mocked necessary dependencies to isolate tests and ensure accurate results. - Updated `vite.config.ts` to set up the testing environment with jsdom and enable global variables for tests.
157 lines
4.7 KiB
TypeScript
157 lines
4.7 KiB
TypeScript
import { app, BrowserWindow, ipcMain } from 'electron'
|
||
import { CONFIG_KEYS } from '@runtime/lib/constants'
|
||
import { setupMainWindow } from './wins';
|
||
import started from 'electron-squirrel-startup'
|
||
import configManager from '@electron/service/config-service'
|
||
import themeManager from '@electron/service/theme-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 { onProviderChange } from '@electron/service/provider-api-service';
|
||
import { gatewayManager } from '@electron/gateway/manager';
|
||
import { dispatchLocalHostApi } from '@electron/api/router';
|
||
import { syncProviderRuntimeSnapshot } from '@electron/service/provider-runtime-sync';
|
||
|
||
// 初始化 updater,确保在 app ready 之前或者之中注册好 IPC
|
||
appUpdater.init();
|
||
|
||
// 注册 hostapi:fetch IPC 代理
|
||
// 模型管理相关接口在本地处理(对齐 ClawX),其余接口代理到远端后端
|
||
const HOST_API_BASE_URL = process.env['ZN_AI_HOST_API_BASE_URL']
|
||
|| process.env['VITE_SERVICE_URL']
|
||
|| 'http://8.138.234.141/ingress';
|
||
|
||
function refreshProviderRuntime(): { warnings: string[] } {
|
||
try {
|
||
return syncProviderRuntimeSnapshot();
|
||
} catch (error) {
|
||
log.error('provider runtime sync failed', error);
|
||
return {
|
||
warnings: [error instanceof Error ? error.message : String(error)],
|
||
};
|
||
}
|
||
}
|
||
|
||
async function requestUpstreamHostApi(path: string, method: string, headers: Record<string, string> | undefined, body: unknown) {
|
||
const url = `${HOST_API_BASE_URL}${path}`;
|
||
try {
|
||
const response = await axios({
|
||
url,
|
||
method,
|
||
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',
|
||
};
|
||
}
|
||
}
|
||
|
||
ipcMain.handle('hostapi:fetch', async (_event, { path, method, headers, body }) => {
|
||
const normalizedMethod = method || 'GET';
|
||
|
||
// 1. 优先本地处理 Host API 路由(逐步对齐 ClawX)
|
||
const localResult = await dispatchLocalHostApi({
|
||
path,
|
||
method: normalizedMethod,
|
||
headers,
|
||
body,
|
||
});
|
||
if (localResult) return localResult;
|
||
|
||
// 2. 其余接口代理到远端后端
|
||
return await requestUpstreamHostApi(path, normalizedMethod, headers, body);
|
||
});
|
||
|
||
// 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(async () => {
|
||
await configManager.init();
|
||
await themeManager.init();
|
||
|
||
gatewayManager.init();
|
||
refreshProviderRuntime();
|
||
|
||
onProviderChange(() => {
|
||
const runtimeSync = refreshProviderRuntime();
|
||
gatewayManager.reloadProviders({
|
||
topics: ['providers', 'models', 'agents'],
|
||
reason: 'providers:changed',
|
||
warnings: runtimeSync.warnings,
|
||
});
|
||
});
|
||
|
||
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();
|
||
}
|
||
});
|