@wrongstack/desktop 0.281.3 → 0.282.1
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/dist/main/main.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
DesktopAgentBridge
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-AMPSW3AD.js";
|
|
4
4
|
|
|
5
5
|
// src/main/main.ts
|
|
6
6
|
import * as path3 from "path";
|
|
@@ -11,7 +11,6 @@ import {
|
|
|
11
11
|
BaseWindow,
|
|
12
12
|
dialog,
|
|
13
13
|
ipcMain,
|
|
14
|
-
Menu,
|
|
15
14
|
screen,
|
|
16
15
|
shell,
|
|
17
16
|
WebContentsView
|
|
@@ -779,7 +778,7 @@ var DesktopRuntimeManager = class extends EventEmitter {
|
|
|
779
778
|
const fallbackSession = lastActiveProjectRoot ? openProjectSessions.find((session) => samePath(session.root, lastActiveProjectRoot)) : void 0;
|
|
780
779
|
const activeSession = activeRuntime?.kind === "project" ? runtimeToSessionState(activeRuntime) : fallbackSession ?? openProjectSessions[0];
|
|
781
780
|
const activeRoot = activeSession?.root;
|
|
782
|
-
const
|
|
781
|
+
const activeRuntimeId = activeSession?.runtimeId ?? null;
|
|
783
782
|
await atomicWrite2(
|
|
784
783
|
this.stateFile,
|
|
785
784
|
`${JSON.stringify(
|
|
@@ -787,7 +786,7 @@ var DesktopRuntimeManager = class extends EventEmitter {
|
|
|
787
786
|
recentProjects: this.recentProjects,
|
|
788
787
|
openProjects,
|
|
789
788
|
openProjectSessions,
|
|
790
|
-
activeRuntimeId
|
|
789
|
+
activeRuntimeId,
|
|
791
790
|
activeProjectRoot: activeRoot ?? null,
|
|
792
791
|
window: this.windowState
|
|
793
792
|
},
|
|
@@ -817,12 +816,12 @@ async function terminateProcessTree(child) {
|
|
|
817
816
|
child.kill("SIGTERM");
|
|
818
817
|
return;
|
|
819
818
|
}
|
|
820
|
-
await new Promise((
|
|
819
|
+
await new Promise((resolve2) => {
|
|
821
820
|
let settled = false;
|
|
822
821
|
const finish = () => {
|
|
823
822
|
if (settled) return;
|
|
824
823
|
settled = true;
|
|
825
|
-
|
|
824
|
+
resolve2();
|
|
826
825
|
};
|
|
827
826
|
const timer = setTimeout(finish, 3e3);
|
|
828
827
|
timer.unref?.();
|
|
@@ -967,11 +966,11 @@ async function findFreePort(startPort, exclude) {
|
|
|
967
966
|
throw new Error(`No free local port found near ${startPort}`);
|
|
968
967
|
}
|
|
969
968
|
function isPortFree(port) {
|
|
970
|
-
return new Promise((
|
|
969
|
+
return new Promise((resolve2) => {
|
|
971
970
|
const server = net.createServer();
|
|
972
|
-
server.once("error", () =>
|
|
971
|
+
server.once("error", () => resolve2(false));
|
|
973
972
|
server.once("listening", () => {
|
|
974
|
-
server.close(() =>
|
|
973
|
+
server.close(() => resolve2(true));
|
|
975
974
|
});
|
|
976
975
|
server.listen(port, "127.0.0.1");
|
|
977
976
|
});
|
|
@@ -981,12 +980,12 @@ function waitForHttpReady(baseUrl, token, timeoutMs) {
|
|
|
981
980
|
const url = new URL(baseUrl);
|
|
982
981
|
url.searchParams.set("token", token);
|
|
983
982
|
url.searchParams.set("shell", "desktop");
|
|
984
|
-
return new Promise((
|
|
983
|
+
return new Promise((resolve2, reject) => {
|
|
985
984
|
const probe = () => {
|
|
986
985
|
const req = http.get(url, (res) => {
|
|
987
986
|
res.resume();
|
|
988
987
|
if (res.statusCode && res.statusCode >= 200 && res.statusCode < 500) {
|
|
989
|
-
|
|
988
|
+
resolve2();
|
|
990
989
|
return;
|
|
991
990
|
}
|
|
992
991
|
retry();
|
|
@@ -1074,6 +1073,9 @@ function preloadPath() {
|
|
|
1074
1073
|
function webuiPreloadPath() {
|
|
1075
1074
|
return fileURLToPath(new URL("../preload/webui-preload.cjs", import.meta.url));
|
|
1076
1075
|
}
|
|
1076
|
+
function desktopSettingsWorkspaceRoot() {
|
|
1077
|
+
return path2.join(wstackGlobalRoot2(), "settings");
|
|
1078
|
+
}
|
|
1077
1079
|
|
|
1078
1080
|
// src/main/main.ts
|
|
1079
1081
|
import { watchProviderConfig } from "@wrongstack/core/storage";
|
|
@@ -1232,14 +1234,356 @@ function isRecord(value) {
|
|
|
1232
1234
|
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
1233
1235
|
}
|
|
1234
1236
|
|
|
1235
|
-
// src/main/
|
|
1236
|
-
var manager = new DesktopRuntimeManager();
|
|
1237
|
-
var bridge = new DesktopAgentBridge();
|
|
1238
|
-
var OPEN_EXTERNAL_ALLOWED_PROTOCOLS = /* @__PURE__ */ new Set(["http:", "https:", "mailto:"]);
|
|
1237
|
+
// src/main/state/constants.ts
|
|
1239
1238
|
var SIDEBAR_WIDTH_WIDE = 292;
|
|
1240
1239
|
var SIDEBAR_WIDTH_MEDIUM = 276;
|
|
1241
1240
|
var SIDEBAR_WIDTH_NARROW = 252;
|
|
1242
1241
|
var SIDEBAR_WIDTH_COLLAPSED = 56;
|
|
1242
|
+
|
|
1243
|
+
// src/main/layout/sidebar.ts
|
|
1244
|
+
function getSidebarWidth(windowWidth, collapsed) {
|
|
1245
|
+
if (collapsed) return SIDEBAR_WIDTH_COLLAPSED;
|
|
1246
|
+
if (windowWidth < 900) return SIDEBAR_WIDTH_NARROW;
|
|
1247
|
+
if (windowWidth < 1180) return SIDEBAR_WIDTH_MEDIUM;
|
|
1248
|
+
return SIDEBAR_WIDTH_WIDE;
|
|
1249
|
+
}
|
|
1250
|
+
|
|
1251
|
+
// src/main/menu/index.ts
|
|
1252
|
+
import { Menu } from "electron";
|
|
1253
|
+
|
|
1254
|
+
// src/main/menu/projects-menu.ts
|
|
1255
|
+
var posix = (await import("path")).posix;
|
|
1256
|
+
function buildProjectsMenu(runtimes, actions, t) {
|
|
1257
|
+
const projectGroups = groupProjectRuntimesForMenu(runtimes);
|
|
1258
|
+
const menu = [
|
|
1259
|
+
{
|
|
1260
|
+
label: t("openProjectEllipsis"),
|
|
1261
|
+
accelerator: "CmdOrCtrl+O",
|
|
1262
|
+
click: () => actions.newSession("")
|
|
1263
|
+
// Will open dialog
|
|
1264
|
+
},
|
|
1265
|
+
{ label: t("registerProjectEllipsis"), click: () => actions.newSession("") },
|
|
1266
|
+
{ type: "separator" }
|
|
1267
|
+
];
|
|
1268
|
+
if (projectGroups.length === 0) {
|
|
1269
|
+
menu.push({ label: t("noOpenProjectSessions"), enabled: false });
|
|
1270
|
+
return menu;
|
|
1271
|
+
}
|
|
1272
|
+
for (const group of projectGroups) {
|
|
1273
|
+
menu.push({
|
|
1274
|
+
label: group.name,
|
|
1275
|
+
submenu: [
|
|
1276
|
+
{
|
|
1277
|
+
label: t("newSession"),
|
|
1278
|
+
click: () => actions.newSession(group.sessions[0]?.id ?? ""),
|
|
1279
|
+
enabled: Boolean(group.sessions[0])
|
|
1280
|
+
},
|
|
1281
|
+
{
|
|
1282
|
+
label: t("revealProjectFolder"),
|
|
1283
|
+
click: () => actions.reveal(group.sessions[0]?.id ?? ""),
|
|
1284
|
+
enabled: Boolean(group.sessions[0])
|
|
1285
|
+
},
|
|
1286
|
+
{ type: "separator" },
|
|
1287
|
+
...group.sessions.map(
|
|
1288
|
+
(runtime, index) => buildSessionMenu(runtime, index + 1, actions, t)
|
|
1289
|
+
)
|
|
1290
|
+
]
|
|
1291
|
+
});
|
|
1292
|
+
}
|
|
1293
|
+
return menu;
|
|
1294
|
+
}
|
|
1295
|
+
function buildSessionMenu(runtime, index, actions, t) {
|
|
1296
|
+
const running = runtime.status === "running";
|
|
1297
|
+
const label = `${t("session")} ${index} \xB7 ${runtime.status}`;
|
|
1298
|
+
return {
|
|
1299
|
+
label,
|
|
1300
|
+
submenu: [
|
|
1301
|
+
{
|
|
1302
|
+
label: t("quickView"),
|
|
1303
|
+
click: () => actions.activate(runtime.id)
|
|
1304
|
+
},
|
|
1305
|
+
{
|
|
1306
|
+
label: "WebUI",
|
|
1307
|
+
enabled: running,
|
|
1308
|
+
submenu: [
|
|
1309
|
+
{
|
|
1310
|
+
label: t("chat"),
|
|
1311
|
+
click: () => actions.activateAndNavigate(runtime.id, { activity: "chat", view: "chat" })
|
|
1312
|
+
},
|
|
1313
|
+
{
|
|
1314
|
+
label: t("focusPrompt"),
|
|
1315
|
+
click: () => actions.activateAndNavigate(runtime.id, { action: "focus-chat" })
|
|
1316
|
+
},
|
|
1317
|
+
{
|
|
1318
|
+
label: t("terminal"),
|
|
1319
|
+
click: () => actions.activateAndNavigate(runtime.id, { terminal: "toggle" })
|
|
1320
|
+
},
|
|
1321
|
+
{
|
|
1322
|
+
label: t("newTerminal"),
|
|
1323
|
+
click: () => actions.activateAndNavigate(runtime.id, { terminal: "new" })
|
|
1324
|
+
},
|
|
1325
|
+
{ type: "separator" },
|
|
1326
|
+
{
|
|
1327
|
+
label: t("files"),
|
|
1328
|
+
click: () => actions.activateAndNavigate(runtime.id, { activity: "files", view: "files" })
|
|
1329
|
+
},
|
|
1330
|
+
{
|
|
1331
|
+
label: t("changes"),
|
|
1332
|
+
click: () => actions.activateAndNavigate(runtime.id, { activity: "changes", view: "changes" })
|
|
1333
|
+
},
|
|
1334
|
+
{
|
|
1335
|
+
label: t("sessions"),
|
|
1336
|
+
click: () => actions.activateAndNavigate(runtime.id, { view: "sessions" })
|
|
1337
|
+
},
|
|
1338
|
+
{
|
|
1339
|
+
label: t("fleetHQ"),
|
|
1340
|
+
click: () => actions.activateAndNavigate(runtime.id, { activity: "officemap", view: "officemap" })
|
|
1341
|
+
},
|
|
1342
|
+
{
|
|
1343
|
+
label: t("settings"),
|
|
1344
|
+
click: () => actions.activateAndNavigate(runtime.id, { view: "settings" })
|
|
1345
|
+
},
|
|
1346
|
+
{ type: "separator" },
|
|
1347
|
+
{
|
|
1348
|
+
label: t("commandPalette"),
|
|
1349
|
+
click: () => actions.activateAndNavigate(runtime.id, { action: "open-command-palette" })
|
|
1350
|
+
},
|
|
1351
|
+
{
|
|
1352
|
+
label: t("modelSwitcher"),
|
|
1353
|
+
click: () => actions.activateAndNavigate(runtime.id, { action: "open-model-switcher" })
|
|
1354
|
+
}
|
|
1355
|
+
]
|
|
1356
|
+
},
|
|
1357
|
+
{ type: "separator" },
|
|
1358
|
+
{
|
|
1359
|
+
label: t("openInBrowser"),
|
|
1360
|
+
enabled: running,
|
|
1361
|
+
click: () => actions.openBrowser(runtime.id)
|
|
1362
|
+
},
|
|
1363
|
+
{
|
|
1364
|
+
label: t("reloadWebui"),
|
|
1365
|
+
enabled: running,
|
|
1366
|
+
click: () => actions.reload(runtime.id)
|
|
1367
|
+
},
|
|
1368
|
+
{
|
|
1369
|
+
label: t("closeSession"),
|
|
1370
|
+
click: () => actions.close(runtime.id)
|
|
1371
|
+
}
|
|
1372
|
+
]
|
|
1373
|
+
};
|
|
1374
|
+
}
|
|
1375
|
+
function groupProjectRuntimesForMenu(runtimes) {
|
|
1376
|
+
const groups = /* @__PURE__ */ new Map();
|
|
1377
|
+
for (const runtime of runtimes) {
|
|
1378
|
+
if (runtime.kind !== "project") continue;
|
|
1379
|
+
const key = normalizeMenuRoot(runtime.root);
|
|
1380
|
+
const existing = groups.get(key);
|
|
1381
|
+
if (existing) {
|
|
1382
|
+
existing.sessions.push(runtime);
|
|
1383
|
+
continue;
|
|
1384
|
+
}
|
|
1385
|
+
groups.set(key, {
|
|
1386
|
+
key,
|
|
1387
|
+
name: posix.basename(runtime.root) || runtime.name,
|
|
1388
|
+
root: runtime.root,
|
|
1389
|
+
sessions: [runtime]
|
|
1390
|
+
});
|
|
1391
|
+
}
|
|
1392
|
+
return [...groups.values()].sort((a, b) => a.name.localeCompare(b.name));
|
|
1393
|
+
}
|
|
1394
|
+
function normalizeMenuRoot(root) {
|
|
1395
|
+
return root.replace(/\\/g, "/").replace(/\/+$/g, "").toLowerCase();
|
|
1396
|
+
}
|
|
1397
|
+
|
|
1398
|
+
// src/main/menu/sections.ts
|
|
1399
|
+
function buildFileMenu(ctx, _actions, hasActiveRuntime, hasActiveProjectWebui, active, navigate, getActiveRuntimeId) {
|
|
1400
|
+
return {
|
|
1401
|
+
label: ctx.t("file"),
|
|
1402
|
+
submenu: [
|
|
1403
|
+
{
|
|
1404
|
+
label: ctx.t("openProjectEllipsis"),
|
|
1405
|
+
accelerator: "CmdOrCtrl+O",
|
|
1406
|
+
click: () => void ctx.openProject()
|
|
1407
|
+
},
|
|
1408
|
+
{ label: ctx.t("registerProjectEllipsis"), click: () => void ctx.registerProject() },
|
|
1409
|
+
{
|
|
1410
|
+
label: ctx.t("removeActiveFromRegistry"),
|
|
1411
|
+
enabled: hasActiveProjectWebui,
|
|
1412
|
+
click: () => {
|
|
1413
|
+
if (active?.kind === "project") void ctx.unregisterProject(active.root);
|
|
1414
|
+
}
|
|
1415
|
+
},
|
|
1416
|
+
{ type: "separator" },
|
|
1417
|
+
{
|
|
1418
|
+
label: ctx.t("newSessionForActive"),
|
|
1419
|
+
accelerator: "CmdOrCtrl+N",
|
|
1420
|
+
enabled: hasActiveProjectWebui,
|
|
1421
|
+
click: () => void ctx.openProjectSession(active?.id)
|
|
1422
|
+
},
|
|
1423
|
+
{
|
|
1424
|
+
label: ctx.t("settings"),
|
|
1425
|
+
accelerator: "CmdOrCtrl+,",
|
|
1426
|
+
click: () => {
|
|
1427
|
+
if (hasActiveRuntime) navigate({ view: "settings" });
|
|
1428
|
+
else void ctx.openSettings();
|
|
1429
|
+
}
|
|
1430
|
+
},
|
|
1431
|
+
{ type: "separator" },
|
|
1432
|
+
{
|
|
1433
|
+
label: ctx.t("closeActiveRuntime"),
|
|
1434
|
+
accelerator: "CmdOrCtrl+W",
|
|
1435
|
+
enabled: hasActiveRuntime,
|
|
1436
|
+
click: () => {
|
|
1437
|
+
const id = getActiveRuntimeId();
|
|
1438
|
+
if (id) void ctx.closeRuntime(id);
|
|
1439
|
+
}
|
|
1440
|
+
},
|
|
1441
|
+
{ type: "separator" },
|
|
1442
|
+
{
|
|
1443
|
+
role: process.platform === "darwin" ? "close" : "quit"
|
|
1444
|
+
}
|
|
1445
|
+
]
|
|
1446
|
+
};
|
|
1447
|
+
}
|
|
1448
|
+
function buildWorkspaceMenu(ctx, _actions, hasActiveWebui, prefs, navigate) {
|
|
1449
|
+
const yoloChecked = prefs?.yolo === true;
|
|
1450
|
+
const nextPredictionChecked = prefs?.nextPrediction === true;
|
|
1451
|
+
const contextAutoCompactChecked = prefs?.contextAutoCompact === true;
|
|
1452
|
+
const webuiItem = (item) => ({
|
|
1453
|
+
...item,
|
|
1454
|
+
enabled: item.enabled ?? hasActiveWebui
|
|
1455
|
+
});
|
|
1456
|
+
return {
|
|
1457
|
+
label: ctx.t("workspace"),
|
|
1458
|
+
submenu: [
|
|
1459
|
+
webuiItem({
|
|
1460
|
+
label: ctx.t("openChat"),
|
|
1461
|
+
accelerator: "CmdOrCtrl+1",
|
|
1462
|
+
click: () => navigate({ activity: "chat", view: "chat" })
|
|
1463
|
+
}),
|
|
1464
|
+
webuiItem({
|
|
1465
|
+
label: ctx.t("focusPrompt"),
|
|
1466
|
+
accelerator: "CmdOrCtrl+/",
|
|
1467
|
+
click: () => navigate({ action: "focus-chat" })
|
|
1468
|
+
}),
|
|
1469
|
+
webuiItem({
|
|
1470
|
+
label: ctx.t("toggleTerminal"),
|
|
1471
|
+
accelerator: "CmdOrCtrl+`",
|
|
1472
|
+
click: () => navigate({ terminal: "toggle" })
|
|
1473
|
+
}),
|
|
1474
|
+
webuiItem({ label: ctx.t("newTerminal"), click: () => navigate({ terminal: "new" }) }),
|
|
1475
|
+
{ type: "separator" },
|
|
1476
|
+
webuiItem({
|
|
1477
|
+
label: ctx.t("commandPalette"),
|
|
1478
|
+
accelerator: "CmdOrCtrl+K",
|
|
1479
|
+
click: () => navigate({ action: "open-command-palette" })
|
|
1480
|
+
}),
|
|
1481
|
+
webuiItem({
|
|
1482
|
+
label: ctx.t("quickModelSwitcher"),
|
|
1483
|
+
accelerator: "CmdOrCtrl+M",
|
|
1484
|
+
click: () => navigate({ action: "open-model-switcher" })
|
|
1485
|
+
}),
|
|
1486
|
+
webuiItem({
|
|
1487
|
+
type: "checkbox",
|
|
1488
|
+
label: ctx.t("yoloMode"),
|
|
1489
|
+
checked: yoloChecked,
|
|
1490
|
+
accelerator: "CmdOrCtrl+Shift+Y",
|
|
1491
|
+
click: () => navigate({ pref: { key: "yolo", toggle: true } })
|
|
1492
|
+
}),
|
|
1493
|
+
webuiItem({
|
|
1494
|
+
type: "checkbox",
|
|
1495
|
+
label: ctx.t("nextPrediction"),
|
|
1496
|
+
checked: nextPredictionChecked,
|
|
1497
|
+
click: () => navigate({ pref: { key: "nextPrediction", toggle: true } })
|
|
1498
|
+
}),
|
|
1499
|
+
webuiItem({
|
|
1500
|
+
type: "checkbox",
|
|
1501
|
+
label: ctx.t("contextAutoCompact"),
|
|
1502
|
+
checked: contextAutoCompactChecked,
|
|
1503
|
+
click: () => navigate({ pref: { key: "contextAutoCompact", toggle: true } })
|
|
1504
|
+
}),
|
|
1505
|
+
{ type: "separator" },
|
|
1506
|
+
webuiItem({
|
|
1507
|
+
label: ctx.t("reloadActiveWebui"),
|
|
1508
|
+
accelerator: "CmdOrCtrl+Shift+R",
|
|
1509
|
+
click: () => void ctx.reloadActiveWebuiView()
|
|
1510
|
+
})
|
|
1511
|
+
]
|
|
1512
|
+
};
|
|
1513
|
+
}
|
|
1514
|
+
function buildViewMenu(ctx) {
|
|
1515
|
+
return {
|
|
1516
|
+
label: ctx.t("view"),
|
|
1517
|
+
submenu: [
|
|
1518
|
+
{
|
|
1519
|
+
type: "checkbox",
|
|
1520
|
+
label: ctx.t("compactDesktopSidebar"),
|
|
1521
|
+
accelerator: "CmdOrCtrl+B",
|
|
1522
|
+
checked: ctx.getShellSidebarCollapsed(),
|
|
1523
|
+
click: () => ctx.setShellSidebarCollapsed(!ctx.getShellSidebarCollapsed())
|
|
1524
|
+
},
|
|
1525
|
+
{ type: "separator" },
|
|
1526
|
+
{ role: "reload" },
|
|
1527
|
+
{ role: "toggleDevTools" },
|
|
1528
|
+
{ type: "separator" },
|
|
1529
|
+
{ role: "resetZoom" },
|
|
1530
|
+
{ role: "zoomIn" },
|
|
1531
|
+
{ role: "zoomOut" },
|
|
1532
|
+
{ type: "separator" },
|
|
1533
|
+
{ role: "togglefullscreen" }
|
|
1534
|
+
]
|
|
1535
|
+
};
|
|
1536
|
+
}
|
|
1537
|
+
|
|
1538
|
+
// src/main/menu/index.ts
|
|
1539
|
+
function configureApplicationMenu(ctx) {
|
|
1540
|
+
const snapshot = ctx.getSnapshot();
|
|
1541
|
+
const active = ctx.getActiveRuntime();
|
|
1542
|
+
const hasActiveRuntime = Boolean(active);
|
|
1543
|
+
const hasActiveWebui = active?.status === "running";
|
|
1544
|
+
const hasActiveProjectWebui = hasActiveWebui && active?.kind === "project";
|
|
1545
|
+
const activeWebuiPrefs = ctx.getActiveWebuiPrefs();
|
|
1546
|
+
const navigate = (command) => {
|
|
1547
|
+
void ctx.dispatchWebuiCommand(command);
|
|
1548
|
+
};
|
|
1549
|
+
const activateAndNavigate = (runtimeId, command) => {
|
|
1550
|
+
void ctx.activateRuntime(runtimeId).then(() => ctx.dispatchWebuiCommand(command));
|
|
1551
|
+
};
|
|
1552
|
+
const reloadRuntimeWebui = (runtimeId) => {
|
|
1553
|
+
void ctx.activateRuntime(runtimeId).then(() => ctx.reloadActiveWebuiView());
|
|
1554
|
+
};
|
|
1555
|
+
const actions = {
|
|
1556
|
+
activate: (runtimeId) => void ctx.activateRuntime(runtimeId),
|
|
1557
|
+
activateAndNavigate,
|
|
1558
|
+
newSession: (runtimeId) => {
|
|
1559
|
+
if (runtimeId) void ctx.openProjectSession(runtimeId);
|
|
1560
|
+
else void ctx.openProject();
|
|
1561
|
+
},
|
|
1562
|
+
openBrowser: (runtimeId) => {
|
|
1563
|
+
const url = ctx.getRuntimeManager().getRuntimeUrlWithToken(runtimeId);
|
|
1564
|
+
if (url) ctx.openExternal(url);
|
|
1565
|
+
},
|
|
1566
|
+
reload: reloadRuntimeWebui,
|
|
1567
|
+
close: (runtimeId) => void ctx.closeRuntime(runtimeId),
|
|
1568
|
+
reveal: (runtimeId) => {
|
|
1569
|
+
const runtime = ctx.getRuntimeManager().getRuntime(runtimeId);
|
|
1570
|
+
if (runtime) ctx.revealInExplorer(runtime.root);
|
|
1571
|
+
}
|
|
1572
|
+
};
|
|
1573
|
+
const template = [
|
|
1574
|
+
buildFileMenu(ctx, actions, hasActiveRuntime, hasActiveProjectWebui, active, navigate, ctx.getActiveRuntimeId),
|
|
1575
|
+
{
|
|
1576
|
+
label: ctx.t("projects"),
|
|
1577
|
+
submenu: buildProjectsMenu(snapshot.runtimes, actions, ctx.t)
|
|
1578
|
+
},
|
|
1579
|
+
buildWorkspaceMenu(ctx, actions, hasActiveWebui, activeWebuiPrefs, navigate),
|
|
1580
|
+
buildViewMenu(ctx)
|
|
1581
|
+
];
|
|
1582
|
+
Menu.setApplicationMenu(Menu.buildFromTemplate(template));
|
|
1583
|
+
}
|
|
1584
|
+
|
|
1585
|
+
// src/main/main.ts
|
|
1586
|
+
var OPEN_EXTERNAL_ALLOWED_PROTOCOLS = /* @__PURE__ */ new Set(["http:", "https:", "mailto:"]);
|
|
1243
1587
|
var MIN_WINDOW_WIDTH2 = 760;
|
|
1244
1588
|
var MIN_WINDOW_HEIGHT2 = 520;
|
|
1245
1589
|
var MAX_PENDING_WEBUI_COMMANDS = 50;
|
|
@@ -1248,17 +1592,8 @@ var WEBUI_COMMAND_FALLBACK_MS = 350;
|
|
|
1248
1592
|
var WEBUI_COMMAND_ACK_TIMEOUT_MS = 2e3;
|
|
1249
1593
|
app.setAppUserModelId("com.wrongstack.desktop");
|
|
1250
1594
|
app.setPath("userData", path3.join(wstackGlobalRoot3(), "desktop", "electron-profile"));
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
try {
|
|
1254
|
-
protocol = new URL(target).protocol;
|
|
1255
|
-
} catch {
|
|
1256
|
-
return;
|
|
1257
|
-
}
|
|
1258
|
-
if (OPEN_EXTERNAL_ALLOWED_PROTOCOLS.has(protocol)) {
|
|
1259
|
-
void shell.openExternal(target);
|
|
1260
|
-
}
|
|
1261
|
-
}
|
|
1595
|
+
var manager = new DesktopRuntimeManager();
|
|
1596
|
+
var bridge = new DesktopAgentBridge();
|
|
1262
1597
|
var mainWindow = null;
|
|
1263
1598
|
var shellView = null;
|
|
1264
1599
|
var webuiViews = /* @__PURE__ */ new Map();
|
|
@@ -1269,77 +1604,56 @@ var shellSidebarCollapsed = false;
|
|
|
1269
1604
|
var pendingWebuiCommandAcks = /* @__PURE__ */ new Map();
|
|
1270
1605
|
var saveWindowStateTimer = null;
|
|
1271
1606
|
var quittingAfterCleanup = false;
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
minWidth: MIN_WINDOW_WIDTH2,
|
|
1282
|
-
minHeight: MIN_WINDOW_HEIGHT2,
|
|
1283
|
-
title: tMain("windowTitle"),
|
|
1284
|
-
backgroundColor: "#111217"
|
|
1285
|
-
};
|
|
1286
|
-
if (windowState?.x !== void 0) windowOptions.x = windowState.x;
|
|
1287
|
-
if (windowState?.y !== void 0) windowOptions.y = windowState.y;
|
|
1288
|
-
mainWindow = new BaseWindow(windowOptions);
|
|
1289
|
-
if (windowState?.maximized) {
|
|
1290
|
-
mainWindow.maximize();
|
|
1607
|
+
function safeOpenExternal(target) {
|
|
1608
|
+
let protocol;
|
|
1609
|
+
try {
|
|
1610
|
+
protocol = new URL(target).protocol;
|
|
1611
|
+
} catch {
|
|
1612
|
+
return;
|
|
1613
|
+
}
|
|
1614
|
+
if (OPEN_EXTERNAL_ALLOWED_PROTOCOLS.has(protocol)) {
|
|
1615
|
+
void shell.openExternal(target);
|
|
1291
1616
|
}
|
|
1292
|
-
shellView = new WebContentsView({
|
|
1293
|
-
webPreferences: {
|
|
1294
|
-
preload: preloadPath(),
|
|
1295
|
-
contextIsolation: true,
|
|
1296
|
-
nodeIntegration: false,
|
|
1297
|
-
sandbox: false
|
|
1298
|
-
}
|
|
1299
|
-
});
|
|
1300
|
-
mainWindow.contentView.addChildView(shellView);
|
|
1301
|
-
shellView.webContents.once("did-finish-load", () => {
|
|
1302
|
-
shellView?.webContents.send(IPC.localeChanged, getMainLocale());
|
|
1303
|
-
});
|
|
1304
|
-
shellView.webContents.setWindowOpenHandler(({ url }) => {
|
|
1305
|
-
safeOpenExternal(url);
|
|
1306
|
-
return { action: "deny" };
|
|
1307
|
-
});
|
|
1308
|
-
await shellView.webContents.loadFile(rendererIndexPath());
|
|
1309
|
-
mainWindow.on("resize", layoutViews);
|
|
1310
|
-
mainWindow.on("resize", scheduleWindowStateSave);
|
|
1311
|
-
mainWindow.on("move", scheduleWindowStateSave);
|
|
1312
|
-
mainWindow.on("maximize", scheduleWindowStateSave);
|
|
1313
|
-
mainWindow.on("unmaximize", scheduleWindowStateSave);
|
|
1314
|
-
mainWindow.on("close", () => {
|
|
1315
|
-
if (saveWindowStateTimer) {
|
|
1316
|
-
clearTimeout(saveWindowStateTimer);
|
|
1317
|
-
saveWindowStateTimer = null;
|
|
1318
|
-
}
|
|
1319
|
-
void saveWindowState();
|
|
1320
|
-
});
|
|
1321
|
-
mainWindow.on("closed", () => {
|
|
1322
|
-
if (saveWindowStateTimer) {
|
|
1323
|
-
clearTimeout(saveWindowStateTimer);
|
|
1324
|
-
saveWindowStateTimer = null;
|
|
1325
|
-
}
|
|
1326
|
-
mainWindow = null;
|
|
1327
|
-
disposeAllWebuiEntries();
|
|
1328
|
-
shellView = null;
|
|
1329
|
-
activeWebuiRuntimeId = null;
|
|
1330
|
-
webuiStatus = { runtimeId: null, status: "idle" };
|
|
1331
|
-
});
|
|
1332
|
-
layoutViews();
|
|
1333
|
-
syncActiveWebuiView();
|
|
1334
|
-
void restoreLastWorkspace();
|
|
1335
1617
|
}
|
|
1336
|
-
function
|
|
1337
|
-
if (!
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1618
|
+
function sameOrigin(candidate, base) {
|
|
1619
|
+
if (!base) return false;
|
|
1620
|
+
try {
|
|
1621
|
+
return new URL(candidate).origin === new URL(base).origin;
|
|
1622
|
+
} catch {
|
|
1623
|
+
return false;
|
|
1624
|
+
}
|
|
1625
|
+
}
|
|
1626
|
+
function isRecord2(value) {
|
|
1627
|
+
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
1628
|
+
}
|
|
1629
|
+
function runtimeWsUrlOrThrow(runtimeId) {
|
|
1630
|
+
const wsUrl = manager.getRuntimeWsUrlWithToken(runtimeId);
|
|
1631
|
+
if (!wsUrl) throw new Error(`Runtime not found: ${runtimeId}`);
|
|
1632
|
+
return wsUrl;
|
|
1633
|
+
}
|
|
1634
|
+
function setShellSidebarCollapsed(collapsed) {
|
|
1635
|
+
shellSidebarCollapsed = collapsed;
|
|
1636
|
+
layoutWebuiViews();
|
|
1637
|
+
configureApplicationMenu2();
|
|
1638
|
+
if (!shellView || shellView.webContents.isDestroyed()) return;
|
|
1639
|
+
shellView.webContents.send(IPC.shellSidebarCollapsedChanged, shellSidebarCollapsed);
|
|
1640
|
+
}
|
|
1641
|
+
function menuRelevantPrefsChanged(previous, next) {
|
|
1642
|
+
return previous?.yolo !== next?.yolo || previous?.nextPrediction !== next?.nextPrediction || previous?.contextAutoCompact !== next?.contextAutoCompact;
|
|
1643
|
+
}
|
|
1644
|
+
function setEntryWebuiStatus(entry, next) {
|
|
1645
|
+
const previousPrefs = entry.status.prefs;
|
|
1646
|
+
entry.status = {
|
|
1647
|
+
...next,
|
|
1648
|
+
prefs: next.prefs ?? entry.status.prefs,
|
|
1649
|
+
pendingCommands: entry.pendingCommands.length || void 0
|
|
1650
|
+
};
|
|
1651
|
+
if (activeWebuiRuntimeId === entry.runtimeId) {
|
|
1652
|
+
publishWebuiStatus(entry.status);
|
|
1653
|
+
if (menuRelevantPrefsChanged(previousPrefs, entry.status.prefs)) {
|
|
1654
|
+
configureApplicationMenu2();
|
|
1655
|
+
}
|
|
1656
|
+
}
|
|
1343
1657
|
}
|
|
1344
1658
|
function scheduleWindowStateSave() {
|
|
1345
1659
|
if (saveWindowStateTimer) clearTimeout(saveWindowStateTimer);
|
|
@@ -1359,36 +1673,51 @@ async function saveWindowState() {
|
|
|
1359
1673
|
maximized: mainWindow.isMaximized()
|
|
1360
1674
|
});
|
|
1361
1675
|
}
|
|
1362
|
-
function
|
|
1676
|
+
function validatedWindowState(state) {
|
|
1677
|
+
if (!state) return null;
|
|
1678
|
+
if (state.width < MIN_WINDOW_WIDTH2 || state.height < MIN_WINDOW_HEIGHT2) return null;
|
|
1679
|
+
if (state.x === void 0 || state.y === void 0) return state;
|
|
1680
|
+
const candidate = {
|
|
1681
|
+
x: state.x,
|
|
1682
|
+
y: state.y,
|
|
1683
|
+
width: state.width,
|
|
1684
|
+
height: state.height
|
|
1685
|
+
};
|
|
1686
|
+
const visibleOnSomeDisplay = screen.getAllDisplays().some((display) => {
|
|
1687
|
+
const area = display.workArea;
|
|
1688
|
+
return rectanglesIntersect(candidate, area);
|
|
1689
|
+
});
|
|
1690
|
+
return visibleOnSomeDisplay ? state : null;
|
|
1691
|
+
}
|
|
1692
|
+
function rectanglesIntersect(left, right) {
|
|
1693
|
+
return left.x < right.x + right.width && left.x + left.width > right.x && left.y < right.y + right.height && left.y + left.height > right.y;
|
|
1694
|
+
}
|
|
1695
|
+
function layoutViews() {
|
|
1696
|
+
if (!mainWindow || !shellView) return;
|
|
1697
|
+
const size = mainWindow.getContentSize();
|
|
1698
|
+
const width = size[0] ?? 0;
|
|
1699
|
+
const height = size[1] ?? 0;
|
|
1700
|
+
shellView.setBounds({ x: 0, y: 0, width, height });
|
|
1701
|
+
layoutWebuiViews();
|
|
1702
|
+
}
|
|
1703
|
+
function layoutWebuiViews() {
|
|
1363
1704
|
if (!mainWindow) return;
|
|
1364
1705
|
const size = mainWindow.getContentSize();
|
|
1365
|
-
const width =
|
|
1366
|
-
const height =
|
|
1706
|
+
const width = size[0] ?? 0;
|
|
1707
|
+
const height = size[1] ?? 0;
|
|
1367
1708
|
const snapshot = manager.snapshot();
|
|
1368
1709
|
const active = snapshot.runtimes.find((runtime) => runtime.id === snapshot.activeRuntimeId);
|
|
1369
|
-
const sidebarWidth =
|
|
1710
|
+
const sidebarWidth = getSidebarWidth(width, shellSidebarCollapsed);
|
|
1370
1711
|
const contentWidth = Math.max(0, width - sidebarWidth);
|
|
1371
1712
|
for (const entry of webuiViews.values()) {
|
|
1372
|
-
|
|
1713
|
+
const runtime = snapshot.runtimes.find((r) => r.id === entry.runtimeId);
|
|
1714
|
+
if (active?.id === entry.runtimeId && runtime?.status === "running") {
|
|
1373
1715
|
entry.view.setBounds({ x: sidebarWidth, y: 0, width: contentWidth, height });
|
|
1374
1716
|
} else {
|
|
1375
1717
|
entry.view.setBounds({ x: sidebarWidth, y: 0, width: 0, height });
|
|
1376
1718
|
}
|
|
1377
1719
|
}
|
|
1378
1720
|
}
|
|
1379
|
-
function desktopSidebarWidth(windowWidth) {
|
|
1380
|
-
if (shellSidebarCollapsed) return SIDEBAR_WIDTH_COLLAPSED;
|
|
1381
|
-
if (windowWidth < 900) return SIDEBAR_WIDTH_NARROW;
|
|
1382
|
-
if (windowWidth < 1180) return SIDEBAR_WIDTH_MEDIUM;
|
|
1383
|
-
return SIDEBAR_WIDTH_WIDE;
|
|
1384
|
-
}
|
|
1385
|
-
function setShellSidebarCollapsed(collapsed) {
|
|
1386
|
-
shellSidebarCollapsed = collapsed;
|
|
1387
|
-
layoutWebuiViews();
|
|
1388
|
-
configureApplicationMenu();
|
|
1389
|
-
if (!shellView || shellView.webContents.isDestroyed()) return;
|
|
1390
|
-
shellView.webContents.send(IPC.shellSidebarCollapsedChanged, shellSidebarCollapsed);
|
|
1391
|
-
}
|
|
1392
1721
|
function ensureWebuiEntry(runtimeId) {
|
|
1393
1722
|
if (!mainWindow) return null;
|
|
1394
1723
|
const existing = webuiViews.get(runtimeId);
|
|
@@ -1436,11 +1765,7 @@ function ensureWebuiEntry(runtimeId) {
|
|
|
1436
1765
|
});
|
|
1437
1766
|
view.webContents.on("did-fail-load", (_event, errorCode, errorDescription) => {
|
|
1438
1767
|
if (webuiViews.get(runtimeId) !== entry || errorCode === -3) return;
|
|
1439
|
-
setEntryWebuiStatus(entry, {
|
|
1440
|
-
runtimeId,
|
|
1441
|
-
status: "error",
|
|
1442
|
-
error: errorDescription
|
|
1443
|
-
});
|
|
1768
|
+
setEntryWebuiStatus(entry, { runtimeId, status: "error", error: errorDescription });
|
|
1444
1769
|
});
|
|
1445
1770
|
view.webContents.on("render-process-gone", (_event, details) => {
|
|
1446
1771
|
if (webuiViews.get(runtimeId) !== entry) return;
|
|
@@ -1453,6 +1778,48 @@ function ensureWebuiEntry(runtimeId) {
|
|
|
1453
1778
|
webuiViews.set(runtimeId, entry);
|
|
1454
1779
|
return entry;
|
|
1455
1780
|
}
|
|
1781
|
+
function attachWebuiEntry(entry) {
|
|
1782
|
+
if (!mainWindow) return;
|
|
1783
|
+
if (entry.attached) return;
|
|
1784
|
+
mainWindow.contentView.addChildView(entry.view);
|
|
1785
|
+
entry.attached = true;
|
|
1786
|
+
}
|
|
1787
|
+
function disposeWebuiEntry(entry) {
|
|
1788
|
+
webuiViews.delete(entry.runtimeId);
|
|
1789
|
+
entry.pendingCommands.length = 0;
|
|
1790
|
+
settlePendingWebuiCommandAcksForRuntime(entry.runtimeId, false);
|
|
1791
|
+
if (entry.pendingFlushTimer) {
|
|
1792
|
+
clearTimeout(entry.pendingFlushTimer);
|
|
1793
|
+
entry.pendingFlushTimer = null;
|
|
1794
|
+
}
|
|
1795
|
+
if (mainWindow && entry.attached) {
|
|
1796
|
+
mainWindow.contentView.removeChildView(entry.view);
|
|
1797
|
+
}
|
|
1798
|
+
entry.attached = false;
|
|
1799
|
+
if (!entry.view.webContents.isDestroyed()) {
|
|
1800
|
+
entry.view.webContents.close();
|
|
1801
|
+
}
|
|
1802
|
+
if (activeWebuiRuntimeId === entry.runtimeId) activeWebuiRuntimeId = null;
|
|
1803
|
+
}
|
|
1804
|
+
function disposeAllWebuiEntries() {
|
|
1805
|
+
for (const entry of Array.from(webuiViews.values())) {
|
|
1806
|
+
disposeWebuiEntry(entry);
|
|
1807
|
+
}
|
|
1808
|
+
webuiViews.clear();
|
|
1809
|
+
}
|
|
1810
|
+
function pruneWebuiEntries(runtimeIds) {
|
|
1811
|
+
const live = new Set(runtimeIds);
|
|
1812
|
+
for (const [id, entry] of webuiViews) {
|
|
1813
|
+
if (!live.has(id)) {
|
|
1814
|
+
disposeWebuiEntry(entry);
|
|
1815
|
+
}
|
|
1816
|
+
}
|
|
1817
|
+
}
|
|
1818
|
+
function findWebuiEntryBySenderId(senderId) {
|
|
1819
|
+
return Array.from(webuiViews.values()).find(
|
|
1820
|
+
(candidate) => candidate.view.webContents.id === senderId
|
|
1821
|
+
);
|
|
1822
|
+
}
|
|
1456
1823
|
function syncActiveWebuiView() {
|
|
1457
1824
|
if (!mainWindow) return;
|
|
1458
1825
|
const snapshot = manager.snapshot();
|
|
@@ -1489,7 +1856,15 @@ function syncActiveWebuiView() {
|
|
|
1489
1856
|
status: "error",
|
|
1490
1857
|
error: err instanceof Error ? err.message : String(err)
|
|
1491
1858
|
});
|
|
1492
|
-
console.error(
|
|
1859
|
+
console.error(
|
|
1860
|
+
JSON.stringify({
|
|
1861
|
+
level: "error",
|
|
1862
|
+
event: "desktop.webui_view_load_failed",
|
|
1863
|
+
runtimeId: active.id,
|
|
1864
|
+
message: err instanceof Error ? err.message : String(err),
|
|
1865
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
1866
|
+
})
|
|
1867
|
+
);
|
|
1493
1868
|
});
|
|
1494
1869
|
}
|
|
1495
1870
|
}
|
|
@@ -1502,28 +1877,6 @@ function publishWebuiStatus(next) {
|
|
|
1502
1877
|
if (!shellView || shellView.webContents.isDestroyed()) return;
|
|
1503
1878
|
shellView.webContents.send(IPC.webuiStatusChanged, webuiStatus);
|
|
1504
1879
|
}
|
|
1505
|
-
function setEntryWebuiStatus(entry, next) {
|
|
1506
|
-
const previousPrefs = entry.status.prefs;
|
|
1507
|
-
entry.status = {
|
|
1508
|
-
...next,
|
|
1509
|
-
prefs: next.prefs ?? entry.status.prefs,
|
|
1510
|
-
pendingCommands: entry.pendingCommands.length || void 0
|
|
1511
|
-
};
|
|
1512
|
-
if (activeWebuiRuntimeId === entry.runtimeId) {
|
|
1513
|
-
publishWebuiStatus(entry.status);
|
|
1514
|
-
if (menuRelevantPrefsChanged(previousPrefs, entry.status.prefs)) {
|
|
1515
|
-
configureApplicationMenu();
|
|
1516
|
-
}
|
|
1517
|
-
}
|
|
1518
|
-
}
|
|
1519
|
-
function menuRelevantPrefsChanged(previous, next) {
|
|
1520
|
-
return previous?.yolo !== next?.yolo || previous?.nextPrediction !== next?.nextPrediction || previous?.contextAutoCompact !== next?.contextAutoCompact;
|
|
1521
|
-
}
|
|
1522
|
-
function runtimeWsUrlOrThrow(runtimeId) {
|
|
1523
|
-
const wsUrl = manager.getRuntimeWsUrlWithToken(runtimeId);
|
|
1524
|
-
if (!wsUrl) throw new Error(`Runtime not found: ${runtimeId}`);
|
|
1525
|
-
return wsUrl;
|
|
1526
|
-
}
|
|
1527
1880
|
function broadcastLocaleToEmbeddedWebuis(locale) {
|
|
1528
1881
|
for (const entry of webuiViews.values()) {
|
|
1529
1882
|
if (entry.view.webContents.isDestroyed()) continue;
|
|
@@ -1533,103 +1886,25 @@ function broadcastLocaleToEmbeddedWebuis(locale) {
|
|
|
1533
1886
|
}
|
|
1534
1887
|
}
|
|
1535
1888
|
}
|
|
1536
|
-
function
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
ipcMain.handle(IPC.getWebuiStatus, () => webuiStatus);
|
|
1540
|
-
ipcMain.handle(
|
|
1541
|
-
IPC.navigateWebui,
|
|
1542
|
-
async (_event, command) => dispatchWebuiCommand(command)
|
|
1543
|
-
);
|
|
1544
|
-
ipcMain.handle(IPC.reloadWebui, async () => reloadActiveWebuiView());
|
|
1545
|
-
ipcMain.handle(IPC.setShellSidebarCollapsed, (_event, collapsed) => {
|
|
1546
|
-
setShellSidebarCollapsed(collapsed === true);
|
|
1547
|
-
return true;
|
|
1548
|
-
});
|
|
1549
|
-
ipcMain.handle(IPC.openSettings, async () => openSettings());
|
|
1550
|
-
ipcMain.handle(IPC.openProjectSession, async (_event, runtimeId) => {
|
|
1551
|
-
return openProjectSession(runtimeId);
|
|
1552
|
-
});
|
|
1553
|
-
ipcMain.handle(IPC.openProject, async (_event, requestedRoot) => {
|
|
1554
|
-
return openProject(requestedRoot);
|
|
1555
|
-
});
|
|
1556
|
-
ipcMain.handle(IPC.registerProject, async (_event, requestedRoot) => {
|
|
1557
|
-
return registerProject(requestedRoot);
|
|
1558
|
-
});
|
|
1559
|
-
ipcMain.handle(IPC.unregisterProject, async (_event, root) => {
|
|
1560
|
-
return unregisterProject(root);
|
|
1561
|
-
});
|
|
1562
|
-
ipcMain.handle(IPC.activateRuntime, async (_event, id) => {
|
|
1563
|
-
return activateRuntime(id);
|
|
1564
|
-
});
|
|
1565
|
-
ipcMain.handle(IPC.closeRuntime, async (_event, id) => {
|
|
1566
|
-
return closeRuntime(id);
|
|
1567
|
-
});
|
|
1568
|
-
ipcMain.handle(
|
|
1569
|
-
IPC.sendMessage,
|
|
1570
|
-
async (_event, id, content) => bridge.sendMessage(id, runtimeWsUrlOrThrow(id), content)
|
|
1571
|
-
);
|
|
1572
|
-
ipcMain.handle(
|
|
1573
|
-
IPC.abortRuntime,
|
|
1574
|
-
async (_event, id) => bridge.abort(id, runtimeWsUrlOrThrow(id))
|
|
1575
|
-
);
|
|
1576
|
-
ipcMain.handle(IPC.openRuntimeInBrowser, async (_event, id) => {
|
|
1577
|
-
const url = manager.getRuntimeUrlWithToken(id);
|
|
1578
|
-
if (url) safeOpenExternal(url);
|
|
1579
|
-
});
|
|
1580
|
-
ipcMain.handle(IPC.revealRuntimeRoot, async (_event, id) => {
|
|
1581
|
-
const runtime = manager.getRuntime(id);
|
|
1582
|
-
if (runtime) await shell.openPath(runtime.root);
|
|
1583
|
-
});
|
|
1584
|
-
ipcMain.on(IPC.webuiReadyChanged, (event, ready) => {
|
|
1585
|
-
const entry = findWebuiEntryBySenderId(event.sender.id);
|
|
1586
|
-
if (!entry) return;
|
|
1587
|
-
entry.bridgeReady = ready === true;
|
|
1588
|
-
if (entry.bridgeReady) {
|
|
1589
|
-
setEntryWebuiStatus(entry, { ...entry.status, status: "ready" });
|
|
1590
|
-
schedulePendingWebuiFlush(entry);
|
|
1591
|
-
} else if (entry.status.status === "ready") {
|
|
1592
|
-
setEntryWebuiStatus(entry, { ...entry.status, status: "loading" });
|
|
1593
|
-
}
|
|
1594
|
-
});
|
|
1595
|
-
ipcMain.on(IPC.webuiPrefsChanged, (event, prefs) => {
|
|
1596
|
-
const entry = findWebuiEntryBySenderId(event.sender.id);
|
|
1597
|
-
if (!entry) return;
|
|
1598
|
-
const sanitized = sanitizeWebuiPrefs(prefs);
|
|
1599
|
-
if (Object.keys(sanitized).length === 0) return;
|
|
1600
|
-
setEntryWebuiStatus(entry, {
|
|
1601
|
-
...entry.status,
|
|
1602
|
-
prefs: { ...entry.status.prefs ?? {}, ...sanitized }
|
|
1603
|
-
});
|
|
1604
|
-
});
|
|
1605
|
-
ipcMain.on(
|
|
1606
|
-
IPC.webuiCommandAck,
|
|
1607
|
-
(event, requestId, handled, _message) => {
|
|
1608
|
-
const entry = findWebuiEntryBySenderId(event.sender.id);
|
|
1609
|
-
if (!entry || typeof requestId !== "string") return;
|
|
1610
|
-
const pending = pendingWebuiCommandAcks.get(requestId);
|
|
1611
|
-
if (!pending || pending.runtimeId !== entry.runtimeId) return;
|
|
1612
|
-
settlePendingWebuiCommandAck(requestId, handled === true);
|
|
1613
|
-
}
|
|
1614
|
-
);
|
|
1889
|
+
function getActiveWebuiEntry() {
|
|
1890
|
+
const activeId = manager.snapshot().activeRuntimeId;
|
|
1891
|
+
return activeId ? webuiViews.get(activeId) : void 0;
|
|
1615
1892
|
}
|
|
1616
|
-
function
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
);
|
|
1893
|
+
async function isWebuiCommandBridgeReady(entry) {
|
|
1894
|
+
if (webuiViews.get(entry.runtimeId) !== entry || !entry.url) return false;
|
|
1895
|
+
return entry.bridgeReady;
|
|
1620
1896
|
}
|
|
1621
|
-
function
|
|
1622
|
-
|
|
1623
|
-
if (
|
|
1624
|
-
|
|
1625
|
-
if (typeof prefs["nextPrediction"] === "boolean") next.nextPrediction = prefs["nextPrediction"];
|
|
1626
|
-
if (typeof prefs["contextAutoCompact"] === "boolean") {
|
|
1627
|
-
next.contextAutoCompact = prefs["contextAutoCompact"];
|
|
1897
|
+
function queueWebuiCommand(entry, command) {
|
|
1898
|
+
entry.pendingCommands.push(command);
|
|
1899
|
+
if (entry.pendingCommands.length > MAX_PENDING_WEBUI_COMMANDS) {
|
|
1900
|
+
entry.pendingCommands.splice(0, entry.pendingCommands.length - MAX_PENDING_WEBUI_COMMANDS);
|
|
1628
1901
|
}
|
|
1629
|
-
|
|
1902
|
+
entry.pendingFlushAttempts = 0;
|
|
1903
|
+
setEntryWebuiStatus(entry, entry.status);
|
|
1630
1904
|
}
|
|
1631
|
-
function
|
|
1632
|
-
|
|
1905
|
+
function nextWebuiCommandRequestId(runtimeId) {
|
|
1906
|
+
webuiCommandSequence += 1;
|
|
1907
|
+
return `${runtimeId}:${Date.now()}:${webuiCommandSequence}`;
|
|
1633
1908
|
}
|
|
1634
1909
|
async function dispatchWebuiCommand(commandInput) {
|
|
1635
1910
|
const command = normalizeDesktopWebuiCommand(commandInput);
|
|
@@ -1665,7 +1940,15 @@ async function reloadActiveWebuiView() {
|
|
|
1665
1940
|
status: "error",
|
|
1666
1941
|
error: err instanceof Error ? err.message : String(err)
|
|
1667
1942
|
});
|
|
1668
|
-
console.error(
|
|
1943
|
+
console.error(
|
|
1944
|
+
JSON.stringify({
|
|
1945
|
+
level: "error",
|
|
1946
|
+
event: "desktop.webui_view_reload_failed",
|
|
1947
|
+
runtimeId: entry.runtimeId,
|
|
1948
|
+
message: err instanceof Error ? err.message : String(err),
|
|
1949
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
1950
|
+
})
|
|
1951
|
+
);
|
|
1669
1952
|
return false;
|
|
1670
1953
|
});
|
|
1671
1954
|
}
|
|
@@ -1673,7 +1956,7 @@ async function dispatchWebuiCommandNow(entry, command) {
|
|
|
1673
1956
|
if (webuiViews.get(entry.runtimeId) !== entry || !entry.url) return false;
|
|
1674
1957
|
const requestId = nextWebuiCommandRequestId(entry.runtimeId);
|
|
1675
1958
|
const commandWithRequestId = { ...command, requestId };
|
|
1676
|
-
return new Promise((
|
|
1959
|
+
return new Promise((resolve2) => {
|
|
1677
1960
|
const fallbackTimer = setTimeout(() => {
|
|
1678
1961
|
const pending = pendingWebuiCommandAcks.get(requestId);
|
|
1679
1962
|
if (!pending) return;
|
|
@@ -1686,7 +1969,7 @@ async function dispatchWebuiCommandNow(entry, command) {
|
|
|
1686
1969
|
runtimeId: entry.runtimeId,
|
|
1687
1970
|
timer,
|
|
1688
1971
|
fallbackTimer,
|
|
1689
|
-
resolve:
|
|
1972
|
+
resolve: resolve2
|
|
1690
1973
|
});
|
|
1691
1974
|
try {
|
|
1692
1975
|
entry.view.webContents.send(IPC.webuiCommand, commandWithRequestId);
|
|
@@ -1702,10 +1985,6 @@ function sendWebuiCommandDomFallback(entry, command) {
|
|
|
1702
1985
|
if (webuiViews.get(entry.runtimeId) !== entry || entry.view.webContents.isDestroyed()) return;
|
|
1703
1986
|
void entry.view.webContents.executeJavaScript(buildWebuiCommandFallbackScript(command), true).catch(() => void 0);
|
|
1704
1987
|
}
|
|
1705
|
-
function nextWebuiCommandRequestId(runtimeId) {
|
|
1706
|
-
webuiCommandSequence += 1;
|
|
1707
|
-
return `${runtimeId}:${Date.now()}:${webuiCommandSequence}`;
|
|
1708
|
-
}
|
|
1709
1988
|
function settlePendingWebuiCommandAck(requestId, handled) {
|
|
1710
1989
|
const pending = pendingWebuiCommandAcks.get(requestId);
|
|
1711
1990
|
if (!pending) return;
|
|
@@ -1763,73 +2042,20 @@ function schedulePendingWebuiFlush(entry) {
|
|
|
1763
2042
|
void flushPendingWebuiCommands(entry);
|
|
1764
2043
|
}, 250);
|
|
1765
2044
|
}
|
|
1766
|
-
async function
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
2045
|
+
async function openProject(requestedRoot) {
|
|
2046
|
+
let projectRoot = requestedRoot;
|
|
2047
|
+
if (!projectRoot) {
|
|
2048
|
+
const result = await dialog.showOpenDialog({
|
|
2049
|
+
title: tMain("openProject"),
|
|
2050
|
+
properties: ["openDirectory"]
|
|
2051
|
+
});
|
|
2052
|
+
projectRoot = result.filePaths[0];
|
|
1774
2053
|
}
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
return activeId ? webuiViews.get(activeId) : void 0;
|
|
1781
|
-
}
|
|
1782
|
-
function attachWebuiEntry(entry) {
|
|
1783
|
-
if (!mainWindow) return;
|
|
1784
|
-
if (entry.attached) return;
|
|
1785
|
-
mainWindow.contentView.addChildView(entry.view);
|
|
1786
|
-
entry.attached = true;
|
|
1787
|
-
}
|
|
1788
|
-
function pruneWebuiEntries(runtimeIds) {
|
|
1789
|
-
const live = new Set(runtimeIds);
|
|
1790
|
-
for (const [id, entry] of webuiViews) {
|
|
1791
|
-
if (!live.has(id)) {
|
|
1792
|
-
disposeWebuiEntry(entry);
|
|
1793
|
-
}
|
|
1794
|
-
}
|
|
1795
|
-
}
|
|
1796
|
-
function disposeWebuiEntry(entry) {
|
|
1797
|
-
webuiViews.delete(entry.runtimeId);
|
|
1798
|
-
entry.pendingCommands.length = 0;
|
|
1799
|
-
settlePendingWebuiCommandAcksForRuntime(entry.runtimeId, false);
|
|
1800
|
-
if (entry.pendingFlushTimer) {
|
|
1801
|
-
clearTimeout(entry.pendingFlushTimer);
|
|
1802
|
-
entry.pendingFlushTimer = null;
|
|
1803
|
-
}
|
|
1804
|
-
if (mainWindow && entry.attached) {
|
|
1805
|
-
mainWindow.contentView.removeChildView(entry.view);
|
|
1806
|
-
}
|
|
1807
|
-
entry.attached = false;
|
|
1808
|
-
if (!entry.view.webContents.isDestroyed()) {
|
|
1809
|
-
entry.view.webContents.close();
|
|
1810
|
-
}
|
|
1811
|
-
if (activeWebuiRuntimeId === entry.runtimeId) activeWebuiRuntimeId = null;
|
|
1812
|
-
}
|
|
1813
|
-
function disposeAllWebuiEntries() {
|
|
1814
|
-
for (const entry of Array.from(webuiViews.values())) {
|
|
1815
|
-
disposeWebuiEntry(entry);
|
|
1816
|
-
}
|
|
1817
|
-
webuiViews.clear();
|
|
1818
|
-
}
|
|
1819
|
-
async function openProject(requestedRoot) {
|
|
1820
|
-
let projectRoot = requestedRoot;
|
|
1821
|
-
if (!projectRoot) {
|
|
1822
|
-
const result = await dialog.showOpenDialog({
|
|
1823
|
-
title: tMain("openProject"),
|
|
1824
|
-
properties: ["openDirectory"]
|
|
1825
|
-
});
|
|
1826
|
-
projectRoot = result.filePaths[0];
|
|
1827
|
-
}
|
|
1828
|
-
if (!projectRoot) return manager.snapshot();
|
|
1829
|
-
await manager.openProject(projectRoot);
|
|
1830
|
-
syncActiveWebuiView();
|
|
1831
|
-
broadcastState();
|
|
1832
|
-
return manager.snapshot();
|
|
2054
|
+
if (!projectRoot) return manager.snapshot();
|
|
2055
|
+
await manager.openProject(projectRoot);
|
|
2056
|
+
syncActiveWebuiView();
|
|
2057
|
+
broadcastState();
|
|
2058
|
+
return manager.snapshot();
|
|
1833
2059
|
}
|
|
1834
2060
|
async function registerProject(requestedRoot) {
|
|
1835
2061
|
let projectRoot = requestedRoot;
|
|
@@ -1899,395 +2125,254 @@ async function restoreLastWorkspace() {
|
|
|
1899
2125
|
syncActiveWebuiView();
|
|
1900
2126
|
broadcastState();
|
|
1901
2127
|
}
|
|
1902
|
-
function
|
|
1903
|
-
return manager.snapshot().activeRuntimeId;
|
|
1904
|
-
}
|
|
1905
|
-
function buildProjectsMenu(runtimes, actions) {
|
|
1906
|
-
const projectGroups = groupProjectRuntimesForMenu(runtimes);
|
|
1907
|
-
const menu = [
|
|
1908
|
-
{
|
|
1909
|
-
label: tMain("openProjectEllipsis"),
|
|
1910
|
-
accelerator: "CmdOrCtrl+O",
|
|
1911
|
-
click: () => void openProject()
|
|
1912
|
-
},
|
|
1913
|
-
{ label: tMain("registerProjectEllipsis"), click: () => void registerProject() },
|
|
1914
|
-
{ type: "separator" }
|
|
1915
|
-
];
|
|
1916
|
-
if (projectGroups.length === 0) {
|
|
1917
|
-
menu.push({ label: tMain("noOpenProjectSessions"), enabled: false });
|
|
1918
|
-
return menu;
|
|
1919
|
-
}
|
|
1920
|
-
for (const group of projectGroups) {
|
|
1921
|
-
menu.push({
|
|
1922
|
-
label: group.name,
|
|
1923
|
-
submenu: [
|
|
1924
|
-
{
|
|
1925
|
-
label: tMain("newSession"),
|
|
1926
|
-
click: () => actions.newSession(group.sessions[0]?.id ?? ""),
|
|
1927
|
-
enabled: Boolean(group.sessions[0])
|
|
1928
|
-
},
|
|
1929
|
-
{
|
|
1930
|
-
label: tMain("revealProjectFolder"),
|
|
1931
|
-
click: () => actions.reveal(group.sessions[0]?.id ?? ""),
|
|
1932
|
-
enabled: Boolean(group.sessions[0])
|
|
1933
|
-
},
|
|
1934
|
-
{ type: "separator" },
|
|
1935
|
-
...group.sessions.map((runtime, index) => buildSessionMenu(runtime, index + 1, actions))
|
|
1936
|
-
]
|
|
1937
|
-
});
|
|
1938
|
-
}
|
|
1939
|
-
return menu;
|
|
1940
|
-
}
|
|
1941
|
-
function buildSessionMenu(runtime, index, actions) {
|
|
1942
|
-
const running = runtime.status === "running";
|
|
1943
|
-
const label = `${tMain("session")} ${index} \xB7 ${runtime.status}`;
|
|
2128
|
+
function createMenuContext() {
|
|
1944
2129
|
return {
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
click: () => actions.activateAndNavigate(runtime.id, { action: "open-command-palette" })
|
|
1996
|
-
},
|
|
1997
|
-
{
|
|
1998
|
-
label: tMain("modelSwitcher"),
|
|
1999
|
-
click: () => actions.activateAndNavigate(runtime.id, { action: "open-model-switcher" })
|
|
2000
|
-
}
|
|
2001
|
-
]
|
|
2002
|
-
},
|
|
2003
|
-
{ type: "separator" },
|
|
2004
|
-
{
|
|
2005
|
-
label: tMain("openInBrowser"),
|
|
2006
|
-
enabled: running,
|
|
2007
|
-
click: () => actions.openBrowser(runtime.id)
|
|
2008
|
-
},
|
|
2009
|
-
{
|
|
2010
|
-
label: tMain("reloadWebui"),
|
|
2011
|
-
enabled: running,
|
|
2012
|
-
click: () => actions.reload(runtime.id)
|
|
2013
|
-
},
|
|
2014
|
-
{
|
|
2015
|
-
label: tMain("closeSession"),
|
|
2016
|
-
click: () => actions.close(runtime.id)
|
|
2017
|
-
}
|
|
2018
|
-
]
|
|
2130
|
+
getSnapshot: () => manager.snapshot(),
|
|
2131
|
+
getActiveRuntime: () => {
|
|
2132
|
+
const snapshot = manager.snapshot();
|
|
2133
|
+
return snapshot.runtimes.find((r) => r.id === snapshot.activeRuntimeId);
|
|
2134
|
+
},
|
|
2135
|
+
getActiveWebuiPrefs: () => {
|
|
2136
|
+
const snapshot = manager.snapshot();
|
|
2137
|
+
if (!snapshot.activeRuntimeId) return void 0;
|
|
2138
|
+
return webuiViews.get(snapshot.activeRuntimeId)?.status.prefs;
|
|
2139
|
+
},
|
|
2140
|
+
getShellSidebarCollapsed: () => shellSidebarCollapsed,
|
|
2141
|
+
t: tMain,
|
|
2142
|
+
getRuntimeManager: () => manager,
|
|
2143
|
+
getWebuiViews: () => webuiViews,
|
|
2144
|
+
dispatchWebuiCommand,
|
|
2145
|
+
reloadActiveWebuiView,
|
|
2146
|
+
activateRuntime: async (id) => {
|
|
2147
|
+
await activateRuntime(id);
|
|
2148
|
+
},
|
|
2149
|
+
openProject: async () => {
|
|
2150
|
+
await openProject();
|
|
2151
|
+
},
|
|
2152
|
+
registerProject: async () => {
|
|
2153
|
+
await registerProject();
|
|
2154
|
+
},
|
|
2155
|
+
openSettings: async () => {
|
|
2156
|
+
await openSettings();
|
|
2157
|
+
},
|
|
2158
|
+
openProjectSession: async (id) => {
|
|
2159
|
+
await openProjectSession(id);
|
|
2160
|
+
},
|
|
2161
|
+
closeRuntime: async (id) => {
|
|
2162
|
+
await closeRuntime(id);
|
|
2163
|
+
},
|
|
2164
|
+
unregisterProject: async (root) => {
|
|
2165
|
+
await unregisterProject(root);
|
|
2166
|
+
},
|
|
2167
|
+
getActiveRuntimeId: () => activeWebuiRuntimeId,
|
|
2168
|
+
setShellSidebarCollapsed: (collapsed) => {
|
|
2169
|
+
setShellSidebarCollapsed(collapsed);
|
|
2170
|
+
},
|
|
2171
|
+
restoreLastWorkspace: async () => {
|
|
2172
|
+
await restoreLastWorkspace();
|
|
2173
|
+
},
|
|
2174
|
+
openExternal: (url) => {
|
|
2175
|
+
shell.openExternal(url);
|
|
2176
|
+
},
|
|
2177
|
+
revealInExplorer: (root) => {
|
|
2178
|
+
void shell.openPath(root);
|
|
2179
|
+
}
|
|
2019
2180
|
};
|
|
2020
2181
|
}
|
|
2021
|
-
function
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2182
|
+
function configureApplicationMenu2() {
|
|
2183
|
+
configureApplicationMenu(createMenuContext());
|
|
2184
|
+
}
|
|
2185
|
+
function registerIpcHandlers() {
|
|
2186
|
+
ipcMain.handle(IPC.getState, () => manager.snapshot());
|
|
2187
|
+
ipcMain.handle(
|
|
2188
|
+
IPC.getConversation,
|
|
2189
|
+
(_event, runtimeId) => bridge.snapshot(runtimeId)
|
|
2190
|
+
);
|
|
2191
|
+
ipcMain.handle(IPC.getWebuiStatus, () => webuiStatus);
|
|
2192
|
+
ipcMain.handle(
|
|
2193
|
+
IPC.navigateWebui,
|
|
2194
|
+
async (_event, command) => dispatchWebuiCommand(command)
|
|
2195
|
+
);
|
|
2196
|
+
ipcMain.handle(IPC.reloadWebui, async () => reloadActiveWebuiView());
|
|
2197
|
+
ipcMain.handle(IPC.setShellSidebarCollapsed, (_event, collapsed) => {
|
|
2198
|
+
setShellSidebarCollapsed(collapsed === true);
|
|
2199
|
+
return true;
|
|
2200
|
+
});
|
|
2201
|
+
ipcMain.handle(IPC.openSettings, async () => openSettings());
|
|
2202
|
+
ipcMain.handle(
|
|
2203
|
+
IPC.openProjectSession,
|
|
2204
|
+
async (_event, runtimeId) => openProjectSession(runtimeId)
|
|
2205
|
+
);
|
|
2206
|
+
ipcMain.handle(
|
|
2207
|
+
IPC.openProject,
|
|
2208
|
+
async (_event, requestedRoot) => openProject(requestedRoot)
|
|
2209
|
+
);
|
|
2210
|
+
ipcMain.handle(
|
|
2211
|
+
IPC.registerProject,
|
|
2212
|
+
async (_event, requestedRoot) => registerProject(requestedRoot)
|
|
2213
|
+
);
|
|
2214
|
+
ipcMain.handle(IPC.unregisterProject, async (_event, root) => unregisterProject(root));
|
|
2215
|
+
ipcMain.handle(IPC.activateRuntime, async (_event, id) => activateRuntime(id));
|
|
2216
|
+
ipcMain.handle(IPC.closeRuntime, async (_event, id) => closeRuntime(id));
|
|
2217
|
+
ipcMain.handle(
|
|
2218
|
+
IPC.sendMessage,
|
|
2219
|
+
async (_event, id, content) => bridge.sendMessage(id, runtimeWsUrlOrThrow(id), content)
|
|
2220
|
+
);
|
|
2221
|
+
ipcMain.handle(
|
|
2222
|
+
IPC.abortRuntime,
|
|
2223
|
+
async (_event, id) => bridge.abort(id, runtimeWsUrlOrThrow(id))
|
|
2224
|
+
);
|
|
2225
|
+
ipcMain.handle(IPC.openRuntimeInBrowser, async (_event, id) => {
|
|
2226
|
+
const url = manager.getRuntimeUrlWithToken(id);
|
|
2227
|
+
if (url) safeOpenExternal(url);
|
|
2228
|
+
});
|
|
2229
|
+
ipcMain.handle(IPC.revealRuntimeRoot, async (_event, id) => {
|
|
2230
|
+
const runtime = manager.getRuntime(id);
|
|
2231
|
+
if (runtime) void shell.openPath(runtime.root);
|
|
2232
|
+
});
|
|
2233
|
+
ipcMain.on(IPC.webuiReadyChanged, (event, ready) => {
|
|
2234
|
+
const entry = findWebuiEntryBySenderId(event.sender.id);
|
|
2235
|
+
if (!entry) return;
|
|
2236
|
+
entry.bridgeReady = ready === true;
|
|
2237
|
+
if (entry.bridgeReady) {
|
|
2238
|
+
setEntryWebuiStatus(entry, { ...entry.status, status: "ready" });
|
|
2239
|
+
schedulePendingWebuiFlush(entry);
|
|
2240
|
+
} else if (entry.status.status === "ready") {
|
|
2241
|
+
setEntryWebuiStatus(entry, { ...entry.status, status: "loading" });
|
|
2030
2242
|
}
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2243
|
+
});
|
|
2244
|
+
ipcMain.on(IPC.webuiPrefsChanged, (event, prefs) => {
|
|
2245
|
+
const entry = findWebuiEntryBySenderId(event.sender.id);
|
|
2246
|
+
if (!entry) return;
|
|
2247
|
+
const next = {};
|
|
2248
|
+
if (isRecord2(prefs) && typeof prefs["yolo"] === "boolean") next.yolo = prefs["yolo"];
|
|
2249
|
+
if (isRecord2(prefs) && typeof prefs["nextPrediction"] === "boolean") {
|
|
2250
|
+
next.nextPrediction = prefs["nextPrediction"];
|
|
2251
|
+
}
|
|
2252
|
+
if (isRecord2(prefs) && typeof prefs["contextAutoCompact"] === "boolean") {
|
|
2253
|
+
next.contextAutoCompact = prefs["contextAutoCompact"];
|
|
2254
|
+
}
|
|
2255
|
+
if (Object.keys(next).length === 0) return;
|
|
2256
|
+
setEntryWebuiStatus(entry, {
|
|
2257
|
+
...entry.status,
|
|
2258
|
+
prefs: { ...entry.status.prefs ?? {}, ...next }
|
|
2036
2259
|
});
|
|
2037
|
-
}
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2260
|
+
});
|
|
2261
|
+
ipcMain.on(
|
|
2262
|
+
IPC.webuiCommandAck,
|
|
2263
|
+
(event, requestId, handled, _message) => {
|
|
2264
|
+
const entry = findWebuiEntryBySenderId(event.sender.id);
|
|
2265
|
+
if (!entry || typeof requestId !== "string") return;
|
|
2266
|
+
const pending = pendingWebuiCommandAcks.get(requestId);
|
|
2267
|
+
if (!pending || pending.runtimeId !== entry.runtimeId) return;
|
|
2268
|
+
settlePendingWebuiCommandAck(requestId, handled === true);
|
|
2269
|
+
}
|
|
2270
|
+
);
|
|
2271
|
+
ipcMain.on(IPC.setLocale, (_event, locale) => {
|
|
2272
|
+
setMainLocale(locale);
|
|
2273
|
+
configureApplicationMenu2();
|
|
2274
|
+
broadcastLocaleToEmbeddedWebuis(locale);
|
|
2275
|
+
void writeUiLocale(locale);
|
|
2276
|
+
});
|
|
2042
2277
|
}
|
|
2043
|
-
function
|
|
2044
|
-
const
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2278
|
+
async function boot() {
|
|
2279
|
+
const locale = await readUiLocale();
|
|
2280
|
+
if (locale) setMainLocale(locale);
|
|
2281
|
+
const shellUrl = rendererIndexPath();
|
|
2282
|
+
shellView = new WebContentsView({
|
|
2283
|
+
webPreferences: {
|
|
2284
|
+
preload: preloadPath(),
|
|
2285
|
+
contextIsolation: true,
|
|
2286
|
+
nodeIntegration: false,
|
|
2287
|
+
sandbox: false
|
|
2288
|
+
}
|
|
2289
|
+
});
|
|
2290
|
+
shellView.webContents.setWindowOpenHandler(({ url }) => {
|
|
2291
|
+
safeOpenExternal(url);
|
|
2292
|
+
return { action: "deny" };
|
|
2293
|
+
});
|
|
2294
|
+
await shellView.webContents.loadURL(shellUrl);
|
|
2295
|
+
const prevState = validatedWindowState(manager.getWindowState());
|
|
2296
|
+
const defaultWidth = 1180;
|
|
2297
|
+
const defaultHeight = 720;
|
|
2298
|
+
const winOptions = {
|
|
2299
|
+
width: prevState?.width ?? defaultWidth,
|
|
2300
|
+
height: prevState?.height ?? defaultHeight,
|
|
2301
|
+
show: false,
|
|
2302
|
+
minWidth: MIN_WINDOW_WIDTH2,
|
|
2303
|
+
minHeight: MIN_WINDOW_HEIGHT2,
|
|
2304
|
+
title: tMain("windowTitle")
|
|
2052
2305
|
};
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
2306
|
+
if (prevState) {
|
|
2307
|
+
if (prevState.x !== void 0) winOptions.x = prevState.x;
|
|
2308
|
+
if (prevState.y !== void 0) winOptions.y = prevState.y;
|
|
2309
|
+
}
|
|
2310
|
+
mainWindow = new BaseWindow(winOptions);
|
|
2311
|
+
mainWindow.on("resized", scheduleWindowStateSave);
|
|
2312
|
+
mainWindow.on("moved", scheduleWindowStateSave);
|
|
2313
|
+
mainWindow.on("maximize", scheduleWindowStateSave);
|
|
2314
|
+
mainWindow.on("unmaximize", scheduleWindowStateSave);
|
|
2315
|
+
if (prevState?.maximized) {
|
|
2316
|
+
mainWindow.maximize();
|
|
2317
|
+
}
|
|
2318
|
+
mainWindow.contentView.addChildView(shellView);
|
|
2319
|
+
layoutViews();
|
|
2320
|
+
registerIpcHandlers();
|
|
2321
|
+
configureApplicationMenu2();
|
|
2322
|
+
mainWindow.on("resize", layoutViews);
|
|
2323
|
+
bridge.on("changed", (conversation) => {
|
|
2324
|
+
if (!shellView || shellView.webContents.isDestroyed()) return;
|
|
2325
|
+
shellView.webContents.send(IPC.conversationChanged, conversation);
|
|
2065
2326
|
});
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
label: tMain("newSessionForActive"),
|
|
2086
|
-
accelerator: "CmdOrCtrl+N",
|
|
2087
|
-
enabled: hasActiveProjectWebui,
|
|
2088
|
-
click: () => void openProjectSession(active?.id)
|
|
2089
|
-
},
|
|
2090
|
-
{
|
|
2091
|
-
label: tMain("settings"),
|
|
2092
|
-
accelerator: "CmdOrCtrl+,",
|
|
2093
|
-
click: () => {
|
|
2094
|
-
if (hasActiveWebui) navigate({ view: "settings" });
|
|
2095
|
-
else void openSettings();
|
|
2096
|
-
}
|
|
2097
|
-
},
|
|
2098
|
-
{ type: "separator" },
|
|
2099
|
-
{
|
|
2100
|
-
label: tMain("closeActiveRuntime"),
|
|
2101
|
-
accelerator: "CmdOrCtrl+W",
|
|
2102
|
-
enabled: hasActiveRuntime,
|
|
2103
|
-
click: () => {
|
|
2104
|
-
const id = activeRuntimeId();
|
|
2105
|
-
if (id) void closeRuntime(id);
|
|
2106
|
-
}
|
|
2107
|
-
},
|
|
2108
|
-
{ type: "separator" },
|
|
2109
|
-
{ role: process.platform === "darwin" ? "close" : "quit" }
|
|
2110
|
-
]
|
|
2111
|
-
},
|
|
2112
|
-
{
|
|
2113
|
-
label: tMain("projects"),
|
|
2114
|
-
submenu: buildProjectsMenu(snapshot.runtimes, {
|
|
2115
|
-
activate: (runtimeId) => void activateRuntime(runtimeId),
|
|
2116
|
-
activateAndNavigate,
|
|
2117
|
-
newSession: (runtimeId) => void openProjectSession(runtimeId),
|
|
2118
|
-
openBrowser: (runtimeId) => {
|
|
2119
|
-
const url = manager.getRuntimeUrlWithToken(runtimeId);
|
|
2120
|
-
if (url) safeOpenExternal(url);
|
|
2121
|
-
},
|
|
2122
|
-
reload: reloadRuntimeWebui,
|
|
2123
|
-
close: (runtimeId) => void closeRuntime(runtimeId),
|
|
2124
|
-
reveal: (runtimeId) => {
|
|
2125
|
-
const runtime = manager.getRuntime(runtimeId);
|
|
2126
|
-
if (runtime) void shell.openPath(runtime.root);
|
|
2127
|
-
}
|
|
2128
|
-
})
|
|
2129
|
-
},
|
|
2130
|
-
{
|
|
2131
|
-
label: tMain("workspace"),
|
|
2132
|
-
submenu: [
|
|
2133
|
-
webuiItem({
|
|
2134
|
-
label: tMain("openChat"),
|
|
2135
|
-
accelerator: "CmdOrCtrl+1",
|
|
2136
|
-
click: () => navigate({ activity: "chat", view: "chat" })
|
|
2137
|
-
}),
|
|
2138
|
-
webuiItem({
|
|
2139
|
-
label: tMain("focusPrompt"),
|
|
2140
|
-
accelerator: "CmdOrCtrl+/",
|
|
2141
|
-
click: () => navigate({ action: "focus-chat" })
|
|
2142
|
-
}),
|
|
2143
|
-
webuiItem({
|
|
2144
|
-
label: tMain("toggleTerminal"),
|
|
2145
|
-
accelerator: "CmdOrCtrl+`",
|
|
2146
|
-
click: () => navigate({ terminal: "toggle" })
|
|
2147
|
-
}),
|
|
2148
|
-
webuiItem({ label: tMain("newTerminal"), click: () => navigate({ terminal: "new" }) }),
|
|
2149
|
-
{ type: "separator" },
|
|
2150
|
-
webuiItem({
|
|
2151
|
-
label: tMain("commandPalette"),
|
|
2152
|
-
accelerator: "CmdOrCtrl+K",
|
|
2153
|
-
click: () => navigate({ action: "open-command-palette" })
|
|
2154
|
-
}),
|
|
2155
|
-
webuiItem({
|
|
2156
|
-
label: tMain("quickModelSwitcher"),
|
|
2157
|
-
accelerator: "CmdOrCtrl+M",
|
|
2158
|
-
click: () => navigate({ action: "open-model-switcher" })
|
|
2159
|
-
}),
|
|
2160
|
-
webuiItem({
|
|
2161
|
-
type: "checkbox",
|
|
2162
|
-
label: tMain("yoloMode"),
|
|
2163
|
-
checked: yoloChecked,
|
|
2164
|
-
accelerator: "CmdOrCtrl+Shift+Y",
|
|
2165
|
-
click: () => navigate({ pref: { key: "yolo", toggle: true } })
|
|
2166
|
-
}),
|
|
2167
|
-
webuiItem({
|
|
2168
|
-
type: "checkbox",
|
|
2169
|
-
label: tMain("nextPrediction"),
|
|
2170
|
-
checked: nextPredictionChecked,
|
|
2171
|
-
click: () => navigate({ pref: { key: "nextPrediction", toggle: true } })
|
|
2172
|
-
}),
|
|
2173
|
-
webuiItem({
|
|
2174
|
-
type: "checkbox",
|
|
2175
|
-
label: tMain("contextAutoCompact"),
|
|
2176
|
-
checked: contextAutoCompactChecked,
|
|
2177
|
-
click: () => navigate({ pref: { key: "contextAutoCompact", toggle: true } })
|
|
2178
|
-
}),
|
|
2179
|
-
{ type: "separator" },
|
|
2180
|
-
webuiItem({
|
|
2181
|
-
label: tMain("reloadActiveWebui"),
|
|
2182
|
-
accelerator: "CmdOrCtrl+Shift+R",
|
|
2183
|
-
click: () => void reloadActiveWebuiView()
|
|
2184
|
-
})
|
|
2185
|
-
]
|
|
2186
|
-
},
|
|
2187
|
-
{
|
|
2188
|
-
label: tMain("view"),
|
|
2189
|
-
submenu: [
|
|
2190
|
-
{
|
|
2191
|
-
type: "checkbox",
|
|
2192
|
-
label: tMain("compactDesktopSidebar"),
|
|
2193
|
-
accelerator: "CmdOrCtrl+B",
|
|
2194
|
-
checked: shellSidebarCollapsed,
|
|
2195
|
-
click: () => setShellSidebarCollapsed(!shellSidebarCollapsed)
|
|
2196
|
-
},
|
|
2197
|
-
{ type: "separator" },
|
|
2198
|
-
{ role: "reload" },
|
|
2199
|
-
{ role: "toggleDevTools" },
|
|
2200
|
-
{ type: "separator" },
|
|
2201
|
-
{ role: "resetZoom" },
|
|
2202
|
-
{ role: "zoomIn" },
|
|
2203
|
-
{ role: "zoomOut" },
|
|
2204
|
-
{ type: "separator" },
|
|
2205
|
-
{ role: "togglefullscreen" }
|
|
2206
|
-
]
|
|
2327
|
+
manager.on("changed", () => {
|
|
2328
|
+
syncActiveWebuiView();
|
|
2329
|
+
configureApplicationMenu2();
|
|
2330
|
+
broadcastState();
|
|
2331
|
+
});
|
|
2332
|
+
let lastWatchedLocale;
|
|
2333
|
+
watchProviderConfig(
|
|
2334
|
+
desktopConfigPaths.globalConfigPath,
|
|
2335
|
+
desktopConfigPaths.vault,
|
|
2336
|
+
(snapshot) => {
|
|
2337
|
+
const updated = snapshot.uiLocale;
|
|
2338
|
+
if (!updated || updated === lastWatchedLocale) return;
|
|
2339
|
+
lastWatchedLocale = updated;
|
|
2340
|
+
setMainLocale(updated);
|
|
2341
|
+
configureApplicationMenu2();
|
|
2342
|
+
broadcastLocaleToEmbeddedWebuis(updated);
|
|
2343
|
+
if (shellView && !shellView.webContents.isDestroyed()) {
|
|
2344
|
+
shellView.webContents.send(IPC.localeChanged, updated);
|
|
2345
|
+
}
|
|
2207
2346
|
}
|
|
2208
|
-
|
|
2209
|
-
|
|
2347
|
+
);
|
|
2348
|
+
mainWindow.on("close", (event) => {
|
|
2349
|
+
if (quittingAfterCleanup) return;
|
|
2350
|
+
event.preventDefault();
|
|
2351
|
+
bridge.closeAll();
|
|
2352
|
+
disposeAllWebuiEntries();
|
|
2353
|
+
void saveWindowState();
|
|
2354
|
+
quittingAfterCleanup = true;
|
|
2355
|
+
app.exit(0);
|
|
2356
|
+
});
|
|
2357
|
+
await restoreLastWorkspace();
|
|
2358
|
+
mainWindow.show();
|
|
2359
|
+
shellView.webContents.focus();
|
|
2210
2360
|
}
|
|
2211
|
-
|
|
2212
|
-
configureApplicationMenu();
|
|
2213
|
-
syncActiveWebuiView();
|
|
2214
|
-
broadcastState();
|
|
2215
|
-
});
|
|
2216
|
-
ipcMain.on(IPC.setLocale, (_event, locale) => {
|
|
2217
|
-
setMainLocale(locale);
|
|
2218
|
-
configureApplicationMenu();
|
|
2219
|
-
broadcastLocaleToEmbeddedWebuis(locale);
|
|
2220
|
-
void writeUiLocale(locale);
|
|
2221
|
-
});
|
|
2222
|
-
watchProviderConfig(
|
|
2223
|
-
desktopConfigPaths.globalConfigPath,
|
|
2224
|
-
desktopConfigPaths.vault,
|
|
2225
|
-
(snapshot) => {
|
|
2226
|
-
if (snapshot.uiLocale === void 0) return;
|
|
2227
|
-
setMainLocale(snapshot.uiLocale);
|
|
2228
|
-
configureApplicationMenu();
|
|
2229
|
-
shellView?.webContents.send(IPC.localeChanged, snapshot.uiLocale);
|
|
2230
|
-
broadcastLocaleToEmbeddedWebuis(snapshot.uiLocale);
|
|
2231
|
-
},
|
|
2232
|
-
{ warn: (m) => console.warn(`Config watcher: ${m}`) }
|
|
2233
|
-
);
|
|
2234
|
-
bridge.on("changed", (conversation) => {
|
|
2235
|
-
shellView?.webContents.send(IPC.conversationChanged, conversation);
|
|
2236
|
-
});
|
|
2361
|
+
app.whenReady().then(boot);
|
|
2237
2362
|
app.on("window-all-closed", () => {
|
|
2238
|
-
|
|
2363
|
+
app.quit();
|
|
2239
2364
|
});
|
|
2240
|
-
app.on("before-quit", (
|
|
2241
|
-
if (
|
|
2242
|
-
|
|
2243
|
-
|
|
2244
|
-
if (saveWindowStateTimer) {
|
|
2245
|
-
clearTimeout(saveWindowStateTimer);
|
|
2246
|
-
saveWindowStateTimer = null;
|
|
2365
|
+
app.on("before-quit", () => {
|
|
2366
|
+
if (mainWindow) {
|
|
2367
|
+
mainWindow.removeAllListeners("close");
|
|
2368
|
+
void saveWindowState();
|
|
2247
2369
|
}
|
|
2248
2370
|
bridge.closeAll();
|
|
2249
|
-
|
|
2371
|
+
disposeAllWebuiEntries();
|
|
2250
2372
|
});
|
|
2251
|
-
app.
|
|
2252
|
-
|
|
2253
|
-
|
|
2254
|
-
|
|
2255
|
-
if (mainWindow === null) void createWindow();
|
|
2256
|
-
});
|
|
2257
|
-
}).catch((err) => {
|
|
2258
|
-
console.error(err);
|
|
2259
|
-
app.exit(1);
|
|
2373
|
+
app.on("activate", () => {
|
|
2374
|
+
if (!mainWindow) return;
|
|
2375
|
+
mainWindow.show();
|
|
2376
|
+
shellView?.webContents.focus();
|
|
2260
2377
|
});
|
|
2261
|
-
function sameOrigin(candidate, base) {
|
|
2262
|
-
if (!base) return false;
|
|
2263
|
-
try {
|
|
2264
|
-
const candidateUrl = new URL(candidate);
|
|
2265
|
-
const baseUrl = new URL(base);
|
|
2266
|
-
return candidateUrl.origin === baseUrl.origin;
|
|
2267
|
-
} catch {
|
|
2268
|
-
return false;
|
|
2269
|
-
}
|
|
2270
|
-
}
|
|
2271
|
-
function desktopSettingsWorkspaceRoot() {
|
|
2272
|
-
return path3.join(wstackGlobalRoot3(), "desktop", "global-settings-workspace");
|
|
2273
|
-
}
|
|
2274
|
-
function validatedWindowState(state) {
|
|
2275
|
-
if (!state) return null;
|
|
2276
|
-
if (state.width < MIN_WINDOW_WIDTH2 || state.height < MIN_WINDOW_HEIGHT2) return null;
|
|
2277
|
-
if (state.x === void 0 || state.y === void 0) return state;
|
|
2278
|
-
const candidate = {
|
|
2279
|
-
x: state.x,
|
|
2280
|
-
y: state.y,
|
|
2281
|
-
width: state.width,
|
|
2282
|
-
height: state.height
|
|
2283
|
-
};
|
|
2284
|
-
const visibleOnSomeDisplay = screen.getAllDisplays().some((display) => {
|
|
2285
|
-
const area = display.workArea;
|
|
2286
|
-
return rectanglesIntersect(candidate, area);
|
|
2287
|
-
});
|
|
2288
|
-
return visibleOnSomeDisplay ? state : null;
|
|
2289
|
-
}
|
|
2290
|
-
function rectanglesIntersect(left, right) {
|
|
2291
|
-
return left.x < right.x + right.width && left.x + left.width > right.x && left.y < right.y + right.height && left.y + left.height > right.y;
|
|
2292
|
-
}
|
|
2293
2378
|
//# sourceMappingURL=main.js.map
|