Files
zn-ai/electron/main.ts
duanshuwen ee72cf7261 feat: refactor HomePage to integrate agents store and update related components
feat: add runtime event handling for providers in ProvidersSection

feat: update routing to include Channels and Agents pages

feat: extend route types and navigation items for Channels and Agents

feat: implement agents store for managing agent data and interactions

fix: update chat store to utilize agents store for agent-related functionality

chore: export agents store from index

fix: enhance runtime types for better event handling

fix: update Vite config to handle dev server URL correctly
2026-04-18 14:56:32 +08:00

169 lines
5.1 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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)],
};
}
}
function shouldPreferUpstreamHostApi(path: string, method: string): boolean {
const pathname = new URL(path, 'http://127.0.0.1').pathname;
return method.toUpperCase() === 'GET' && pathname === '/api/channels/targets';
}
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';
if (shouldPreferUpstreamHostApi(path, normalizedMethod)) {
const upstreamPreferred = await requestUpstreamHostApi(path, normalizedMethod, headers, body);
if (upstreamPreferred.success !== false && upstreamPreferred.ok !== false) {
return upstreamPreferred;
}
}
// 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();
}
});