@youweichen/pi-web-ui 0.46.1 → 0.47.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +23 -0
- package/electron/main.mjs +437 -0
- package/package.json +16 -4
package/README.md
CHANGED
|
@@ -37,6 +37,27 @@ pi-web-ui server install # macOS → launchd, Linux → systemd, Windows →
|
|
|
37
37
|
pi-web-ui server status # start | stop | restart | uninstall also available
|
|
38
38
|
```
|
|
39
39
|
|
|
40
|
+
## Desktop app (Electron)
|
|
41
|
+
|
|
42
|
+
There's also an Electron shell around the same server/UI — a native window
|
|
43
|
+
with a tray icon instead of a browser tab. It's built locally from source
|
|
44
|
+
(not published as an npm package or a GitHub release yet):
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
npm install
|
|
48
|
+
npm run build # build:web + build:server first
|
|
49
|
+
npm run dev:electron # run the shell against the local build
|
|
50
|
+
npm run build:electron:mac # or :win / :linux — produces an installer under release/
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
The Electron window loads the same server as the CLI (`server/index.ts`),
|
|
54
|
+
just spawned as a hidden child process instead of opening a browser tab.
|
|
55
|
+
Desktop-app data lives in `~/.pi-web-desktop` (separate from the CLI's
|
|
56
|
+
`~/.pi-web`, so the two don't fight over `client-state.json` if you run both
|
|
57
|
+
— chat history itself is shared, it lives in `~/.pi/agent`).
|
|
58
|
+
|
|
59
|
+
See `docs/deployment.md` for details.
|
|
60
|
+
|
|
40
61
|
## Notes
|
|
41
62
|
|
|
42
63
|
- Binds to loopback (`127.0.0.1`) by default; set `PI_WEB_HOST` to expose on LAN.
|
|
@@ -47,6 +68,8 @@ pi-web-ui server status # start | stop | restart | uninstall also available
|
|
|
47
68
|
|
|
48
69
|
pi 编码智能体的 Web 聊天界面:浏览器对话、文件树、附件、内置终端、模型管理、声音提醒、中英文切换。全局安装后一条命令启动,支持开机自启(launchd / systemd / Windows 计划任务)。
|
|
49
70
|
|
|
71
|
+
也有一个 Electron 桌面版壳子(原生窗口 + 托盘,不用开浏览器标签页),目前需要本地源码构建(`npm run build:electron:mac/:win/:linux`),还没发到 npm / GitHub Releases,见 `docs/deployment.md`。
|
|
72
|
+
|
|
50
73
|
## License
|
|
51
74
|
|
|
52
75
|
MIT
|
|
@@ -0,0 +1,437 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* pi-web-ui Electron 桌面版主进程。
|
|
3
|
+
*
|
|
4
|
+
* 架构:
|
|
5
|
+
* - 主进程 fork 一个隐藏子进程跑 server(ELECTRON_RUN_AS_NODE=1),
|
|
6
|
+
* server 使用 Electron 内置的 Node 运行时,node-pty 等原生模块
|
|
7
|
+
* 在 electron-builder 打包时自动 rebuild 为 Electron ABI。
|
|
8
|
+
* - BrowserWindow 加载 http://127.0.0.1:{PORT}(server 就绪后)。
|
|
9
|
+
* - 托盘:关闭窗口 → 最小化到托盘;Quit → 真正退出(杀 server 子进程)。
|
|
10
|
+
* - 自动更新:electron-updater + GitHub Releases(当前未接发布 CI,
|
|
11
|
+
* autoUpdater.checkForUpdates() 在没有可用 feed 时会静默失败,不影响使用)。
|
|
12
|
+
*
|
|
13
|
+
* 开发模式(npm run dev:electron):
|
|
14
|
+
* 先构建 web + server(npm run build),然后 electron . 即可。
|
|
15
|
+
* 如有 Vite dev server (:5173),优先加载它获取 HMR。
|
|
16
|
+
*
|
|
17
|
+
* 与当前 server/index.ts 的耦合点(重构时请对照检查):
|
|
18
|
+
* - 就绪标记:server 启动后 stdout 打印 "⚡ pi-web-ui"(见 server/index.ts
|
|
19
|
+
* httpServer.listen 回调),本文件靠这行判断 server 已就绪。
|
|
20
|
+
* - PI_WEB_PKG_ROOT:告诉 server 去哪找 web/dist(resolvePkgRoot()),
|
|
21
|
+
* 打包后指向 process.resourcesPath(electron-builder extraResources)。
|
|
22
|
+
* - PI_WEB_DATA_DIR:桌面版单独用 <home>/.pi-web-desktop,与命令行版
|
|
23
|
+
* 的 <home>/.pi-web 分开,避免两边同时跑互相抢 client-state.json /
|
|
24
|
+
* terminals 等运行时状态(数据都在 SDK 的 ~/.pi/agent 里,两边共享,
|
|
25
|
+
* 不受影响)。
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
import { app, BrowserWindow, Tray, Menu, nativeImage, dialog, Notification } from "electron";
|
|
29
|
+
import { fork } from "node:child_process";
|
|
30
|
+
import { createServer } from "node:net";
|
|
31
|
+
import { existsSync, mkdirSync } from "node:fs";
|
|
32
|
+
import { join, dirname, resolve } from "node:path";
|
|
33
|
+
import { fileURLToPath } from "node:url";
|
|
34
|
+
import { autoUpdater } from "electron-updater";
|
|
35
|
+
|
|
36
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
37
|
+
const ROOT = resolve(__dirname, "..");
|
|
38
|
+
const isDev = !app.isPackaged || !!process.env.DEV;
|
|
39
|
+
|
|
40
|
+
// ── 状态 ──
|
|
41
|
+
/** @type {BrowserWindow | null} */
|
|
42
|
+
let mainWindow = null;
|
|
43
|
+
/** @type {Tray | null} */
|
|
44
|
+
let tray = null;
|
|
45
|
+
/** @type {import("node:child_process").ChildProcess | null} */
|
|
46
|
+
let serverProcess = null;
|
|
47
|
+
let isQuitting = false;
|
|
48
|
+
let serverPort = 0;
|
|
49
|
+
|
|
50
|
+
// ── 路径 ──
|
|
51
|
+
|
|
52
|
+
/** 打包后 server 在 resources/dist/server/index.js */
|
|
53
|
+
function getServerPath() {
|
|
54
|
+
if (isDev) return join(ROOT, "dist", "server", "index.js");
|
|
55
|
+
return join(process.resourcesPath, "dist", "server", "index.js");
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** 打包后 web/dist、extensions 在 resources/ 下(见 electron-builder.yml extraResources) */
|
|
59
|
+
function getPkgRoot() {
|
|
60
|
+
if (isDev) return ROOT;
|
|
61
|
+
return process.resourcesPath;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// ── 端口 ──
|
|
65
|
+
|
|
66
|
+
/** 找一个随机空闲端口(防止端口冲突) */
|
|
67
|
+
async function findFreePort() {
|
|
68
|
+
return new Promise((resolve, reject) => {
|
|
69
|
+
const srv = createServer();
|
|
70
|
+
srv.listen(0, "127.0.0.1", () => {
|
|
71
|
+
const port = /** @type {import("node:net").AddressInfo} */ (srv.address()).port;
|
|
72
|
+
srv.close(() => resolve(port));
|
|
73
|
+
});
|
|
74
|
+
srv.on("error", reject);
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// ── 启动 Server 子进程 ──
|
|
79
|
+
|
|
80
|
+
async function startServer() {
|
|
81
|
+
serverPort = await findFreePort();
|
|
82
|
+
const serverPath = getServerPath();
|
|
83
|
+
|
|
84
|
+
if (!existsSync(serverPath)) {
|
|
85
|
+
dialog.showErrorBox(
|
|
86
|
+
"pi-web-ui 启动失败",
|
|
87
|
+
`找不到 server 入口:${serverPath}\n\n请先执行 npm run build,然后重试。`,
|
|
88
|
+
);
|
|
89
|
+
app.quit();
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// 桌面版数据目录与命令行版分开(见文件头注释),确保存在
|
|
94
|
+
const dataDir = join(
|
|
95
|
+
process.env.HOME || process.env.USERPROFILE || "~",
|
|
96
|
+
".pi-web-desktop",
|
|
97
|
+
);
|
|
98
|
+
mkdirSync(dataDir, { recursive: true });
|
|
99
|
+
|
|
100
|
+
serverProcess = fork(serverPath, [], {
|
|
101
|
+
env: {
|
|
102
|
+
...process.env,
|
|
103
|
+
PORT: String(serverPort),
|
|
104
|
+
PI_WEB_HOST: "127.0.0.1",
|
|
105
|
+
PI_WEB_DATA_DIR: dataDir,
|
|
106
|
+
PI_WEB_NO_BROWSER: "1", // 不要自动打开浏览器(本身也不会打开,桌面版有自己的窗口)
|
|
107
|
+
PI_WEB_PKG_ROOT: getPkgRoot(), // 告诉 server 去哪找 web/dist / extensions
|
|
108
|
+
ELECTRON_RUN_AS_NODE: "1", // 以 Node.js 模式运行(非 Electron)
|
|
109
|
+
},
|
|
110
|
+
stdio: ["ignore", "pipe", "pipe", "ipc"],
|
|
111
|
+
serialization: "json",
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
// 收集 stdout/stderr 用于调试
|
|
115
|
+
let serverOut = "";
|
|
116
|
+
serverProcess.stdout?.on("data", (chunk) => {
|
|
117
|
+
const text = chunk.toString();
|
|
118
|
+
serverOut += text;
|
|
119
|
+
process.stdout.write(`[server] ${text}`);
|
|
120
|
+
});
|
|
121
|
+
serverProcess.stderr?.on("data", (chunk) => {
|
|
122
|
+
const text = chunk.toString();
|
|
123
|
+
serverOut += text;
|
|
124
|
+
process.stderr.write(`[server:err] ${text}`);
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
// 等待 server 就绪:监听 stdout 中的 "⚡ pi-web-ui" 标记(见 server/index.ts)
|
|
128
|
+
await new Promise((resolvePromise, rejectPromise) => {
|
|
129
|
+
const timeout = setTimeout(() => {
|
|
130
|
+
rejectPromise(
|
|
131
|
+
new Error(`Server 启动超时(30s)。最后输出:\n${serverOut.slice(-500)}`),
|
|
132
|
+
);
|
|
133
|
+
}, 30_000);
|
|
134
|
+
|
|
135
|
+
const checkOutput = (chunk) => {
|
|
136
|
+
const text = chunk.toString();
|
|
137
|
+
if (text.includes("⚡ pi-web-ui") || text.includes("http://localhost")) {
|
|
138
|
+
clearTimeout(timeout);
|
|
139
|
+
resolvePromise(undefined);
|
|
140
|
+
}
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
serverProcess.stdout?.on("data", checkOutput);
|
|
144
|
+
serverProcess.on("error", (err) => {
|
|
145
|
+
clearTimeout(timeout);
|
|
146
|
+
rejectPromise(err);
|
|
147
|
+
});
|
|
148
|
+
serverProcess.on("exit", (code) => {
|
|
149
|
+
clearTimeout(timeout);
|
|
150
|
+
if (code !== 0) {
|
|
151
|
+
rejectPromise(new Error(`Server 退出了 (exit code=${code})`));
|
|
152
|
+
}
|
|
153
|
+
});
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
console.log(`[electron] server 就绪,端口 ${serverPort}`);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// ── 窗口 ──
|
|
160
|
+
|
|
161
|
+
function createWindow() {
|
|
162
|
+
mainWindow = new BrowserWindow({
|
|
163
|
+
width: 1200,
|
|
164
|
+
height: 800,
|
|
165
|
+
minWidth: 800,
|
|
166
|
+
minHeight: 600,
|
|
167
|
+
title: "pi-web-ui",
|
|
168
|
+
show: false,
|
|
169
|
+
webPreferences: {
|
|
170
|
+
preload: join(__dirname, "preload.mjs"),
|
|
171
|
+
nodeIntegration: false,
|
|
172
|
+
contextIsolation: true,
|
|
173
|
+
},
|
|
174
|
+
icon: join(__dirname, "icon.png"),
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
// 加载 localhost 上的 server
|
|
178
|
+
const url = `http://127.0.0.1:${serverPort}`;
|
|
179
|
+
mainWindow.loadURL(url);
|
|
180
|
+
|
|
181
|
+
mainWindow.once("ready-to-show", () => {
|
|
182
|
+
mainWindow.show();
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
// 关闭窗口 → 隐藏到托盘(不退出)
|
|
186
|
+
mainWindow.on("close", (event) => {
|
|
187
|
+
if (!isQuitting) {
|
|
188
|
+
event.preventDefault();
|
|
189
|
+
mainWindow.hide();
|
|
190
|
+
return false;
|
|
191
|
+
}
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
mainWindow.on("closed", () => {
|
|
195
|
+
mainWindow = null;
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
// 开发模式打开 DevTools
|
|
199
|
+
if (isDev) {
|
|
200
|
+
mainWindow.webContents.openDevTools({ mode: "detach" });
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// ── 托盘 ──
|
|
205
|
+
|
|
206
|
+
function createTray() {
|
|
207
|
+
const iconPath = join(__dirname, "icon.png");
|
|
208
|
+
const icon = existsSync(iconPath) ? nativeImage.createFromPath(iconPath) : nativeImage.createEmpty();
|
|
209
|
+
tray = new Tray(icon.isEmpty() ? icon : icon.resize({ width: 16, height: 16 }));
|
|
210
|
+
tray.setToolTip("pi-web-ui");
|
|
211
|
+
|
|
212
|
+
const contextMenu = Menu.buildFromTemplate([
|
|
213
|
+
{
|
|
214
|
+
label: "显示窗口",
|
|
215
|
+
click: () => {
|
|
216
|
+
if (mainWindow) {
|
|
217
|
+
mainWindow.show();
|
|
218
|
+
mainWindow.focus();
|
|
219
|
+
}
|
|
220
|
+
},
|
|
221
|
+
},
|
|
222
|
+
{ type: "separator" },
|
|
223
|
+
{
|
|
224
|
+
label: "关于 pi-web-ui",
|
|
225
|
+
click: () => {
|
|
226
|
+
dialog.showMessageBox({
|
|
227
|
+
type: "info",
|
|
228
|
+
title: "关于 pi-web-ui",
|
|
229
|
+
message: `pi-web-ui v${app.getVersion()}`,
|
|
230
|
+
detail: "Web chat interface for the pi coding agent.",
|
|
231
|
+
});
|
|
232
|
+
},
|
|
233
|
+
},
|
|
234
|
+
{ type: "separator" },
|
|
235
|
+
{
|
|
236
|
+
label: "退出",
|
|
237
|
+
click: () => {
|
|
238
|
+
isQuitting = true;
|
|
239
|
+
app.quit();
|
|
240
|
+
},
|
|
241
|
+
},
|
|
242
|
+
]);
|
|
243
|
+
|
|
244
|
+
tray.setContextMenu(contextMenu);
|
|
245
|
+
|
|
246
|
+
// 点击托盘图标 → 显示/隐藏窗口
|
|
247
|
+
tray.on("click", () => {
|
|
248
|
+
if (mainWindow) {
|
|
249
|
+
if (mainWindow.isVisible()) {
|
|
250
|
+
mainWindow.hide();
|
|
251
|
+
} else {
|
|
252
|
+
mainWindow.show();
|
|
253
|
+
mainWindow.focus();
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// ── 菜单 ──
|
|
260
|
+
|
|
261
|
+
function createAppMenu() {
|
|
262
|
+
const template = [
|
|
263
|
+
{
|
|
264
|
+
label: "pi-web-ui",
|
|
265
|
+
submenu: [
|
|
266
|
+
{
|
|
267
|
+
label: "关于 pi-web-ui",
|
|
268
|
+
click: () => {
|
|
269
|
+
dialog.showMessageBox({
|
|
270
|
+
type: "info",
|
|
271
|
+
title: "关于 pi-web-ui",
|
|
272
|
+
message: `pi-web-ui v${app.getVersion()}`,
|
|
273
|
+
detail: "Web chat interface for the pi coding agent.",
|
|
274
|
+
});
|
|
275
|
+
},
|
|
276
|
+
},
|
|
277
|
+
{ type: "separator" },
|
|
278
|
+
{ role: "hide" },
|
|
279
|
+
{ role: "hideOthers" },
|
|
280
|
+
{ role: "unhide" },
|
|
281
|
+
{ type: "separator" },
|
|
282
|
+
{
|
|
283
|
+
label: "退出",
|
|
284
|
+
accelerator: "CmdOrCtrl+Q",
|
|
285
|
+
click: () => {
|
|
286
|
+
isQuitting = true;
|
|
287
|
+
app.quit();
|
|
288
|
+
},
|
|
289
|
+
},
|
|
290
|
+
],
|
|
291
|
+
},
|
|
292
|
+
{
|
|
293
|
+
label: "编辑",
|
|
294
|
+
submenu: [
|
|
295
|
+
{ role: "undo" },
|
|
296
|
+
{ role: "redo" },
|
|
297
|
+
{ type: "separator" },
|
|
298
|
+
{ role: "cut" },
|
|
299
|
+
{ role: "copy" },
|
|
300
|
+
{ role: "paste" },
|
|
301
|
+
{ role: "selectAll" },
|
|
302
|
+
],
|
|
303
|
+
},
|
|
304
|
+
{
|
|
305
|
+
label: "视图",
|
|
306
|
+
submenu: [
|
|
307
|
+
{ role: "reload" },
|
|
308
|
+
{ role: "forceReload" },
|
|
309
|
+
{ role: "toggleDevTools" },
|
|
310
|
+
{ type: "separator" },
|
|
311
|
+
{ role: "resetZoom" },
|
|
312
|
+
{ role: "zoomIn" },
|
|
313
|
+
{ role: "zoomOut" },
|
|
314
|
+
{ type: "separator" },
|
|
315
|
+
{ role: "togglefullscreen" },
|
|
316
|
+
],
|
|
317
|
+
},
|
|
318
|
+
{
|
|
319
|
+
label: "窗口",
|
|
320
|
+
submenu: [{ role: "minimize" }, { role: "close" }],
|
|
321
|
+
},
|
|
322
|
+
];
|
|
323
|
+
|
|
324
|
+
// macOS 需要第一个菜单项是应用名
|
|
325
|
+
const menu = Menu.buildFromTemplate(template);
|
|
326
|
+
Menu.setApplicationMenu(menu);
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
// ── 自动更新 ──
|
|
330
|
+
|
|
331
|
+
function setupAutoUpdater() {
|
|
332
|
+
if (isDev) return; // 开发模式不检查更新
|
|
333
|
+
|
|
334
|
+
autoUpdater.autoDownload = false;
|
|
335
|
+
autoUpdater.autoInstallOnAppQuit = true;
|
|
336
|
+
|
|
337
|
+
// 检查更新(启动后延迟 5s)。当前未配置发布 CI / publish feed,
|
|
338
|
+
// 找不到更新源时会静默失败,不影响正常使用。
|
|
339
|
+
setTimeout(() => {
|
|
340
|
+
autoUpdater.checkForUpdates().catch(() => {
|
|
341
|
+
// 静默失败(无网络 / 无 feed / 超时等)
|
|
342
|
+
});
|
|
343
|
+
}, 5000);
|
|
344
|
+
|
|
345
|
+
autoUpdater.on("update-available", (info) => {
|
|
346
|
+
const notification = new Notification({
|
|
347
|
+
title: "pi-web-ui 更新可用",
|
|
348
|
+
body: `版本 ${info.version} 可下载(当前 ${app.getVersion()})`,
|
|
349
|
+
});
|
|
350
|
+
notification.on("click", () => {
|
|
351
|
+
autoUpdater.downloadUpdate();
|
|
352
|
+
});
|
|
353
|
+
notification.show();
|
|
354
|
+
});
|
|
355
|
+
|
|
356
|
+
autoUpdater.on("update-downloaded", () => {
|
|
357
|
+
const result = dialog.showMessageBoxSync({
|
|
358
|
+
type: "info",
|
|
359
|
+
title: "更新已下载",
|
|
360
|
+
message: "新版本已下载完成,是否立即重启以安装更新?",
|
|
361
|
+
buttons: ["立即重启", "稍后"],
|
|
362
|
+
});
|
|
363
|
+
if (result === 0) {
|
|
364
|
+
autoUpdater.quitAndInstall();
|
|
365
|
+
}
|
|
366
|
+
});
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
// ── 应用生命周期 ──
|
|
370
|
+
|
|
371
|
+
app.setAppUserModelId("com.youweichen.pi-web-ui");
|
|
372
|
+
|
|
373
|
+
// 确保只有一个实例
|
|
374
|
+
const gotLock = app.requestSingleInstanceLock();
|
|
375
|
+
if (!gotLock) {
|
|
376
|
+
app.quit();
|
|
377
|
+
} else {
|
|
378
|
+
app.on("second-instance", () => {
|
|
379
|
+
if (mainWindow) {
|
|
380
|
+
if (mainWindow.isMinimized()) mainWindow.restore();
|
|
381
|
+
mainWindow.show();
|
|
382
|
+
mainWindow.focus();
|
|
383
|
+
}
|
|
384
|
+
});
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
app.whenReady().then(async () => {
|
|
388
|
+
try {
|
|
389
|
+
await startServer();
|
|
390
|
+
} catch (err) {
|
|
391
|
+
dialog.showErrorBox("pi-web-ui 启动失败", err instanceof Error ? err.message : String(err));
|
|
392
|
+
app.quit();
|
|
393
|
+
return;
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
createAppMenu();
|
|
397
|
+
createWindow();
|
|
398
|
+
createTray();
|
|
399
|
+
setupAutoUpdater();
|
|
400
|
+
|
|
401
|
+
app.on("activate", () => {
|
|
402
|
+
if (!mainWindow) createWindow();
|
|
403
|
+
if (mainWindow) {
|
|
404
|
+
mainWindow.show();
|
|
405
|
+
mainWindow.focus();
|
|
406
|
+
}
|
|
407
|
+
});
|
|
408
|
+
});
|
|
409
|
+
|
|
410
|
+
app.on("window-all-closed", () => {
|
|
411
|
+
// macOS 不退出(dock 图标还在)
|
|
412
|
+
// 其他平台退出
|
|
413
|
+
if (process.platform !== "darwin") {
|
|
414
|
+
isQuitting = true;
|
|
415
|
+
app.quit();
|
|
416
|
+
}
|
|
417
|
+
});
|
|
418
|
+
|
|
419
|
+
app.on("before-quit", () => {
|
|
420
|
+
isQuitting = true;
|
|
421
|
+
});
|
|
422
|
+
|
|
423
|
+
app.on("will-quit", () => {
|
|
424
|
+
// 杀 server 子进程
|
|
425
|
+
if (serverProcess) {
|
|
426
|
+
try {
|
|
427
|
+
serverProcess.kill("SIGTERM");
|
|
428
|
+
} catch {
|
|
429
|
+
// 可能已经退出
|
|
430
|
+
}
|
|
431
|
+
serverProcess = null;
|
|
432
|
+
}
|
|
433
|
+
if (tray) {
|
|
434
|
+
tray.destroy();
|
|
435
|
+
tray = null;
|
|
436
|
+
}
|
|
437
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@youweichen/pi-web-ui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.47.0",
|
|
4
4
|
"description": "Web chat interface for the pi coding agent, powered by the pi SDK (@earendil-works/pi-coding-agent) — one-command run, Docker/systemd/launchd deployable",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": {
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
"bin": {
|
|
12
12
|
"pi-web-ui": "bin/pi-web-ui.mjs"
|
|
13
13
|
},
|
|
14
|
+
"main": "electron/main.mjs",
|
|
14
15
|
"files": [
|
|
15
16
|
"bin/",
|
|
16
17
|
"dist/",
|
|
@@ -57,7 +58,14 @@
|
|
|
57
58
|
"test:smoke": "node tests/run-smoke.mjs",
|
|
58
59
|
"check:protocol": "node scripts/check-protocol-sync.mjs",
|
|
59
60
|
"test:freeze": "node tests/freeze-test.mjs",
|
|
60
|
-
"start": "node dist/server/index.js"
|
|
61
|
+
"start": "node dist/server/index.js",
|
|
62
|
+
"start:electron": "electron .",
|
|
63
|
+
"dev:electron": "electron .",
|
|
64
|
+
"build:electron": "electron-builder build",
|
|
65
|
+
"build:electron:mac": "electron-builder build --mac",
|
|
66
|
+
"build:electron:win": "electron-builder build --win",
|
|
67
|
+
"build:electron:linux": "electron-builder build --linux",
|
|
68
|
+
"publish:electron": "electron-builder build --publish always"
|
|
61
69
|
},
|
|
62
70
|
"dependencies": {
|
|
63
71
|
"@earendil-works/pi-coding-agent": "^0.84.2",
|
|
@@ -91,9 +99,13 @@
|
|
|
91
99
|
"tsx": "^4.19.2",
|
|
92
100
|
"typescript": "^5.7.2",
|
|
93
101
|
"vite": "^6.0.3",
|
|
94
|
-
"vitest": "^4.1.11"
|
|
102
|
+
"vitest": "^4.1.11",
|
|
103
|
+
"electron": "^41.0.0",
|
|
104
|
+
"electron-builder": "^25.0.0",
|
|
105
|
+
"electron-updater": "^6.0.0"
|
|
95
106
|
},
|
|
96
107
|
"allowScripts": {
|
|
97
|
-
"node-pty@1.1.0": true
|
|
108
|
+
"node-pty@1.1.0": true,
|
|
109
|
+
"electron@41.10.7": true
|
|
98
110
|
}
|
|
99
111
|
}
|