修改前端架构到Electron ,增加llm_api页面,增加chat SSE渲染,修改若安按钮,卡片名称修改等小功能,增加agent-core,增加SQlite,增加llm UDS Pipe websocke
This commit is contained in:
@@ -0,0 +1,160 @@
|
||||
import { app, BrowserWindow, ipcMain, shell } from "electron";
|
||||
import { join } from "path";
|
||||
import { existsSync, mkdirSync } from "fs";
|
||||
import { spawn } from "child_process";
|
||||
import WebSocket from "ws";
|
||||
import __cjs_mod__ from "node:module";
|
||||
const __filename = import.meta.filename;
|
||||
const __dirname = import.meta.dirname;
|
||||
const require2 = __cjs_mod__.createRequire(import.meta.url);
|
||||
const USER_DATA = join(__dirname, "../../.electron-userdata");
|
||||
app.setPath("userData", USER_DATA);
|
||||
const PATHS = {
|
||||
userData: USER_DATA,
|
||||
data: join(USER_DATA, "data"),
|
||||
// SQLite 数据库
|
||||
workspaces: join(USER_DATA, "workspaces"),
|
||||
// worktree 根目录
|
||||
backups: join(USER_DATA, "backups"),
|
||||
// 数据库备份
|
||||
logs: join(USER_DATA, "logs")
|
||||
// 日志
|
||||
};
|
||||
const AGENT_CORE_PORT = 8787;
|
||||
const AGENT_CORE_PATH = join(__dirname, "../../../agent-core");
|
||||
const AGENT_CORE_ENTRY = "src/index.ts";
|
||||
let agentCoreProcess = null;
|
||||
let agentCoreWs = null;
|
||||
let mainWindow = null;
|
||||
function initUserDataDirs() {
|
||||
for (const dir of Object.values(PATHS)) {
|
||||
if (!existsSync(dir)) {
|
||||
mkdirSync(dir, { recursive: true });
|
||||
}
|
||||
}
|
||||
console.log("[main] userData:", USER_DATA);
|
||||
}
|
||||
function startAgentCore() {
|
||||
return new Promise((resolve, reject) => {
|
||||
const isDev = !!process.env.ELECTRON_RENDERER_URL;
|
||||
const cmd = isDev ? "npx" : "node";
|
||||
const args = isDev ? ["tsx", AGENT_CORE_ENTRY] : ["dist/index.js"];
|
||||
agentCoreProcess = spawn(cmd, args, {
|
||||
cwd: AGENT_CORE_PATH,
|
||||
env: {
|
||||
...process.env,
|
||||
AGENT_CORE_PORT: String(AGENT_CORE_PORT),
|
||||
AGENT_CORE_DB_DIR: PATHS.data
|
||||
},
|
||||
stdio: ["pipe", "pipe", "pipe"],
|
||||
shell: process.platform === "win32"
|
||||
// Windows 上需要 shell 才能找到 npx
|
||||
});
|
||||
agentCoreProcess.stdout?.on("data", (data) => {
|
||||
console.log("[agent-core]", data.toString().trim());
|
||||
});
|
||||
agentCoreProcess.stderr?.on("data", (data) => {
|
||||
console.error("[agent-core]", data.toString().trim());
|
||||
});
|
||||
agentCoreProcess.on("exit", (code) => {
|
||||
console.log(`[agent-core] 进程退出,code=${code}`);
|
||||
agentCoreProcess = null;
|
||||
});
|
||||
let retries = 0;
|
||||
const tryConnect = () => {
|
||||
agentCoreWs = new WebSocket(`ws://localhost:${AGENT_CORE_PORT}`);
|
||||
agentCoreWs.on("open", () => {
|
||||
console.log("[main] 已连接 agent-core WebSocket");
|
||||
resolve();
|
||||
});
|
||||
agentCoreWs.on("error", () => {
|
||||
retries++;
|
||||
if (retries > 20) {
|
||||
reject(new Error("无法连接 agent-core WebSocket"));
|
||||
return;
|
||||
}
|
||||
setTimeout(tryConnect, 500);
|
||||
});
|
||||
};
|
||||
setTimeout(tryConnect, 1e3);
|
||||
});
|
||||
}
|
||||
function callAgentCore(type, data) {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!agentCoreWs || agentCoreWs.readyState !== WebSocket.OPEN) {
|
||||
reject(new Error("agent-core 未连接"));
|
||||
return;
|
||||
}
|
||||
const id = Date.now().toString();
|
||||
const onMessage = (raw) => {
|
||||
const msg = JSON.parse(raw.toString());
|
||||
if (msg.id === id) {
|
||||
agentCoreWs?.off("message", onMessage);
|
||||
if (msg.error) reject(new Error(msg.error));
|
||||
else resolve(msg.data);
|
||||
}
|
||||
};
|
||||
agentCoreWs.on("message", onMessage);
|
||||
agentCoreWs.send(JSON.stringify({ type, data, id }));
|
||||
setTimeout(() => {
|
||||
agentCoreWs?.off("message", onMessage);
|
||||
reject(new Error("agent-core 响应超时"));
|
||||
}, 1e4);
|
||||
});
|
||||
}
|
||||
function createWindow() {
|
||||
mainWindow = new BrowserWindow({
|
||||
width: 1440,
|
||||
height: 900,
|
||||
minWidth: 1024,
|
||||
minHeight: 640,
|
||||
titleBarStyle: "hiddenInset",
|
||||
backgroundColor: "#1f1f1f",
|
||||
webPreferences: {
|
||||
preload: join(__dirname, "../preload/index.mjs"),
|
||||
contextIsolation: true,
|
||||
nodeIntegration: false,
|
||||
sandbox: false
|
||||
}
|
||||
});
|
||||
mainWindow.on("ready-to-show", () => {
|
||||
mainWindow?.show();
|
||||
});
|
||||
mainWindow.webContents.setWindowOpenHandler((details) => {
|
||||
shell.openExternal(details.url);
|
||||
return { action: "deny" };
|
||||
});
|
||||
const rendererUrl = process.env.ELECTRON_RENDERER_URL;
|
||||
if (rendererUrl) {
|
||||
mainWindow.loadURL(rendererUrl);
|
||||
} else {
|
||||
mainWindow.loadFile(join(__dirname, "../renderer/index.html"));
|
||||
}
|
||||
}
|
||||
app.whenReady().then(async () => {
|
||||
initUserDataDirs();
|
||||
try {
|
||||
await startAgentCore();
|
||||
} catch (err) {
|
||||
console.error("[main] agent-core 启动失败:", err);
|
||||
}
|
||||
createWindow();
|
||||
app.on("activate", () => {
|
||||
if (BrowserWindow.getAllWindows().length === 0) createWindow();
|
||||
});
|
||||
});
|
||||
app.on("window-all-closed", () => {
|
||||
if (agentCoreWs) agentCoreWs.close();
|
||||
if (agentCoreProcess) agentCoreProcess.kill();
|
||||
if (process.platform !== "darwin") app.quit();
|
||||
});
|
||||
ipcMain.handle("ping", () => "pong");
|
||||
ipcMain.handle("get-paths", () => PATHS);
|
||||
ipcMain.handle("get-db-path", () => join(PATHS.data, "agent-console.db"));
|
||||
ipcMain.handle("agent:call", async (_event, { type, data }) => {
|
||||
return callAgentCore(type, data);
|
||||
});
|
||||
ipcMain.handle("agent:status", () => ({
|
||||
connected: agentCoreWs?.readyState === WebSocket.OPEN,
|
||||
port: AGENT_CORE_PORT
|
||||
}));
|
||||
@@ -0,0 +1,24 @@
|
||||
import { contextBridge, ipcRenderer } from "electron";
|
||||
contextBridge.exposeInMainWorld("electronAPI", {
|
||||
ping: () => ipcRenderer.invoke("ping"),
|
||||
// 用户数据路径
|
||||
getPaths: () => ipcRenderer.invoke("get-paths"),
|
||||
getDbPath: () => ipcRenderer.invoke("get-db-path"),
|
||||
// ========== agent-core 桥接 ==========
|
||||
/**
|
||||
* 调用 agent-core(请求-响应模式)
|
||||
* @param type 消息类型,如 'llm:save' / 'llm:list' / 'llm:setActive'
|
||||
* @param data 消息数据
|
||||
*/
|
||||
callAgent: (type, data) => ipcRenderer.invoke("agent:call", { type, data }),
|
||||
/**
|
||||
* 检查 agent-core 连接状态
|
||||
*/
|
||||
agentStatus: () => ipcRenderer.invoke("agent:status"),
|
||||
// 预留:流式调用
|
||||
onAgentStream: (callback) => {
|
||||
const listener = (_e, data) => callback(data);
|
||||
ipcRenderer.on("agent-stream", listener);
|
||||
return () => ipcRenderer.removeListener("agent-stream", listener);
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user