@co0ontty/wand 1.14.3 → 1.15.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/dist/message-truncator.js +0 -2
- package/dist/process-manager.js +19 -62
- package/dist/types.d.ts +0 -2
- package/dist/web-ui/content/scripts.js +649 -170
- package/dist/web-ui/content/styles.css +651 -57
- package/package.json +1 -1
|
@@ -71,6 +71,7 @@
|
|
|
71
71
|
suggestionTimer: null,
|
|
72
72
|
terminal: null,
|
|
73
73
|
fitAddon: null,
|
|
74
|
+
terminalFitInProgress: false,
|
|
74
75
|
serializeAddon: null,
|
|
75
76
|
terminalDomView: null,
|
|
76
77
|
terminalDomUpdateTimer: null,
|
|
@@ -109,6 +110,9 @@
|
|
|
109
110
|
loginChecked: false,
|
|
110
111
|
bootstrapping: true,
|
|
111
112
|
sessionsDrawerOpen: false,
|
|
113
|
+
sidebarPinned: (function() {
|
|
114
|
+
try { return localStorage.getItem("wand-sidebar-pinned") === "true"; } catch (e) { return false; }
|
|
115
|
+
})(),
|
|
112
116
|
modalOpen: false,
|
|
113
117
|
presetValue: "",
|
|
114
118
|
cwdValue: "",
|
|
@@ -131,9 +135,14 @@
|
|
|
131
135
|
ws: null,
|
|
132
136
|
wsConnected: false,
|
|
133
137
|
_updateBubbleShown: false,
|
|
138
|
+
notificationHistory: {},
|
|
139
|
+
delayedNotificationTimer: null,
|
|
134
140
|
notifSound: (function() {
|
|
135
141
|
try { var v = localStorage.getItem("wand-notif-sound"); return v === null ? true : v === "true"; } catch (e) { return true; }
|
|
136
142
|
})(),
|
|
143
|
+
notifVolume: (function() {
|
|
144
|
+
try { var v = localStorage.getItem("wand-notif-volume"); return v === null ? 80 : Math.max(0, Math.min(100, parseInt(v, 10) || 80)); } catch (e) { return 80; }
|
|
145
|
+
})(),
|
|
137
146
|
notifBubble: (function() {
|
|
138
147
|
try { var v = localStorage.getItem("wand-notif-bubble"); return v === null ? true : v === "true"; } catch (e) { return true; }
|
|
139
148
|
})(),
|
|
@@ -652,6 +661,7 @@
|
|
|
652
661
|
if (!el) return false;
|
|
653
662
|
switch (kind) {
|
|
654
663
|
case "tool-card":
|
|
664
|
+
case "diff":
|
|
655
665
|
return !el.classList.contains("collapsed");
|
|
656
666
|
case "thinking":
|
|
657
667
|
return el.classList.contains("expanded") && !el.classList.contains("collapsed");
|
|
@@ -672,7 +682,8 @@
|
|
|
672
682
|
function applyExpandedState(el, kind, expanded) {
|
|
673
683
|
if (!el) return;
|
|
674
684
|
switch (kind) {
|
|
675
|
-
case "tool-card":
|
|
685
|
+
case "tool-card":
|
|
686
|
+
case "diff": {
|
|
676
687
|
el.classList.toggle("collapsed", !expanded);
|
|
677
688
|
break;
|
|
678
689
|
}
|
|
@@ -886,8 +897,7 @@
|
|
|
886
897
|
refreshAll();
|
|
887
898
|
requestNotificationPermission();
|
|
888
899
|
if (config.updateAvailable && config.latestVersion) {
|
|
889
|
-
|
|
890
|
-
sendBrowserNotification("Wand \u53d1\u73b0\u65b0\u7248\u672c", "\u5f53\u524d " + (config.currentVersion || "-") + " \u2192 \u6700\u65b0 " + config.latestVersion, { tag: "wand-update" });
|
|
900
|
+
notifyUpdateAvailable(config.currentVersion || "-", config.latestVersion);
|
|
891
901
|
}
|
|
892
902
|
if (state.claudeHistoryExpanded && !state.claudeHistoryLoaded) {
|
|
893
903
|
loadClaudeHistory();
|
|
@@ -932,6 +942,10 @@
|
|
|
932
942
|
// Suppress CSS transitions during initial DOM build
|
|
933
943
|
document.documentElement.classList.add("no-transition");
|
|
934
944
|
|
|
945
|
+
// Apply persisted pin state before rendering
|
|
946
|
+
if (state.sidebarPinned && !isMobileLayout()) {
|
|
947
|
+
state.sessionsDrawerOpen = true;
|
|
948
|
+
}
|
|
935
949
|
app.innerHTML = isLoggedIn ? renderAppShell() : renderLogin();
|
|
936
950
|
// Reset chat render tracking since DOM was fully replaced
|
|
937
951
|
resetChatRenderCache();
|
|
@@ -1083,8 +1097,8 @@
|
|
|
1083
1097
|
|
|
1084
1098
|
return '<div class="app-container">' +
|
|
1085
1099
|
'<div id="sessions-drawer-backdrop" class="drawer-backdrop' + drawerClass + '"></div>' +
|
|
1086
|
-
'<div class="main-layout' + (state.sessionsDrawerOpen ? ' sidebar-open' : '') + '">' +
|
|
1087
|
-
'<aside id="sessions-drawer" class="sidebar' + drawerClass + '">' +
|
|
1100
|
+
'<div class="main-layout' + (state.sessionsDrawerOpen ? ' sidebar-open' : '') + (state.sidebarPinned && !isMobileLayout() ? ' sidebar-pinned' : '') + '">' +
|
|
1101
|
+
'<aside id="sessions-drawer" class="sidebar' + drawerClass + (state.sidebarPinned && !isMobileLayout() ? ' pinned' : '') + '">' +
|
|
1088
1102
|
'<div class="sidebar-header">' +
|
|
1089
1103
|
'<div class="sidebar-header-main">' +
|
|
1090
1104
|
'<div class="topbar-logo-icon">W</div>' +
|
|
@@ -1098,6 +1112,9 @@
|
|
|
1098
1112
|
'<button id="sidebar-refresh-btn" class="btn btn-ghost btn-sm" type="button" title="刷新页面">' +
|
|
1099
1113
|
'<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="23 4 23 10 17 10"/><path d="M20.49 15a9 9 0 1 1-2.12-9.36L23 10"/></svg>' +
|
|
1100
1114
|
'</button>' +
|
|
1115
|
+
'<button id="sidebar-pin-btn" class="btn btn-ghost btn-sm sidebar-pin-toggle' + (state.sidebarPinned ? ' pinned' : '') + '" type="button" title="' + (state.sidebarPinned ? '取消固定侧栏' : '固定侧栏') + '">' +
|
|
1116
|
+
'<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="12" y1="17" x2="12" y2="22"/><path d="M5 17h14v-1.76a2 2 0 0 0-1.11-1.79l-1.78-.9A2 2 0 0 1 15 10.76V6h1a2 2 0 0 0 0-4H8a2 2 0 0 0 0 4h1v4.76a2 2 0 0 1-1.11 1.79l-1.78.9A2 2 0 0 0 5 15.24z"/></svg>' +
|
|
1117
|
+
'</button>' +
|
|
1101
1118
|
'<button id="close-drawer-button" class="btn btn-ghost btn-sm sidebar-close" type="button" aria-label="关闭菜单">×</button>' +
|
|
1102
1119
|
'</div>' +
|
|
1103
1120
|
'</div>' +
|
|
@@ -1304,23 +1321,55 @@
|
|
|
1304
1321
|
function renderSettingsModal() {
|
|
1305
1322
|
return '<section id="settings-modal" class="modal-backdrop hidden">' +
|
|
1306
1323
|
'<div class="modal settings-modal">' +
|
|
1307
|
-
'<div class="modal-header">' +
|
|
1308
|
-
'<
|
|
1324
|
+
'<div class="modal-header settings-modal-header">' +
|
|
1325
|
+
'<div class="settings-modal-title-group">' +
|
|
1326
|
+
'<h2 class="modal-title">设置</h2>' +
|
|
1327
|
+
'<p class="settings-modal-subtitle">调整应用配置、通知、安全和显示偏好</p>' +
|
|
1328
|
+
'</div>' +
|
|
1309
1329
|
'<button id="close-settings-button" class="btn btn-ghost btn-icon">×</button>' +
|
|
1310
1330
|
'</div>' +
|
|
1311
|
-
'<div class="modal-body">' +
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1331
|
+
'<div class="modal-body settings-modal-body">' +
|
|
1332
|
+
'<div class="settings-layout">' +
|
|
1333
|
+
'<aside class="settings-sidebar">' +
|
|
1334
|
+
'<div class="settings-sidebar-header">' +
|
|
1335
|
+
'<div class="settings-sidebar-title">偏好设置</div>' +
|
|
1336
|
+
'<div class="settings-sidebar-hint">左侧切换分区,右侧查看详细说明与选项。</div>' +
|
|
1337
|
+
'</div>' +
|
|
1338
|
+
'<div class="settings-tabs" role="tablist" aria-label="设置分组" aria-orientation="vertical">' +
|
|
1339
|
+
'<button class="settings-tab active" data-tab="about" role="tab" aria-selected="true" aria-controls="settings-tab-about">' +
|
|
1340
|
+
'<span class="settings-tab-main">关于</span>' +
|
|
1341
|
+
'<span class="settings-tab-meta">版本、更新与连接方式</span>' +
|
|
1342
|
+
'</button>' +
|
|
1343
|
+
'<button class="settings-tab" data-tab="general" role="tab" aria-selected="false" aria-controls="settings-tab-general">' +
|
|
1344
|
+
'<span class="settings-tab-main">基本配置</span>' +
|
|
1345
|
+
'<span class="settings-tab-meta">主机、模式、语言、目录</span>' +
|
|
1346
|
+
'</button>' +
|
|
1347
|
+
'<button class="settings-tab" data-tab="notifications" role="tab" aria-selected="false" aria-controls="settings-tab-notifications">' +
|
|
1348
|
+
'<span class="settings-tab-main">通知</span>' +
|
|
1349
|
+
'<span class="settings-tab-meta">提示音与浏览器通知</span>' +
|
|
1350
|
+
'</button>' +
|
|
1351
|
+
'<button class="settings-tab" data-tab="security" role="tab" aria-selected="false" aria-controls="settings-tab-security">' +
|
|
1352
|
+
'<span class="settings-tab-main">安全</span>' +
|
|
1353
|
+
'<span class="settings-tab-meta">密码与证书</span>' +
|
|
1354
|
+
'</button>' +
|
|
1355
|
+
'<button class="settings-tab" data-tab="presets" role="tab" aria-selected="false" aria-controls="settings-tab-presets">' +
|
|
1356
|
+
'<span class="settings-tab-main">命令预设</span>' +
|
|
1357
|
+
'<span class="settings-tab-meta">查看已有预设</span>' +
|
|
1358
|
+
'</button>' +
|
|
1359
|
+
'<button class="settings-tab" data-tab="display" role="tab" aria-selected="false" aria-controls="settings-tab-display">' +
|
|
1360
|
+
'<span class="settings-tab-main">显示</span>' +
|
|
1361
|
+
'<span class="settings-tab-meta">卡片默认展开行为</span>' +
|
|
1362
|
+
'</button>' +
|
|
1363
|
+
'</div>' +
|
|
1364
|
+
'</aside>' +
|
|
1365
|
+
'<div class="settings-content">' +
|
|
1321
1366
|
|
|
1322
1367
|
// About tab
|
|
1323
|
-
'<div class="settings-panel active" id="settings-tab-about">' +
|
|
1368
|
+
'<div class="settings-panel active" id="settings-tab-about" role="tabpanel">' +
|
|
1369
|
+
'<div class="settings-panel-header">' +
|
|
1370
|
+
'<h3 class="settings-panel-title">关于 Wand</h3>' +
|
|
1371
|
+
'<p class="settings-panel-desc">查看版本信息、更新状态和 Android App 连接方式。</p>' +
|
|
1372
|
+
'</div>' +
|
|
1324
1373
|
'<div class="settings-about-info">' +
|
|
1325
1374
|
'<div class="settings-about-row"><span class="settings-label">包名</span><span class="settings-value" id="settings-pkg-name">-</span></div>' +
|
|
1326
1375
|
'<div class="settings-about-row"><span class="settings-label">当前版本</span><span class="settings-value" id="settings-version">-</span></div>' +
|
|
@@ -1367,18 +1416,40 @@
|
|
|
1367
1416
|
'</div>' +
|
|
1368
1417
|
|
|
1369
1418
|
// Notifications tab
|
|
1370
|
-
'<div class="settings-panel" id="settings-tab-notifications">' +
|
|
1419
|
+
'<div class="settings-panel" id="settings-tab-notifications" role="tabpanel">' +
|
|
1420
|
+
'<div class="settings-panel-header">' +
|
|
1421
|
+
'<h3 class="settings-panel-title">通知</h3>' +
|
|
1422
|
+
'<p class="settings-panel-desc">设置提示音、系统通知和浏览器通知的行为。</p>' +
|
|
1423
|
+
'</div>' +
|
|
1371
1424
|
'<div class="settings-section-title">\u901a\u77e5\u504f\u597d</div>' +
|
|
1372
1425
|
'<div class="field field-inline">' +
|
|
1373
1426
|
'<input id="cfg-notif-sound" type="checkbox" class="field-checkbox" />' +
|
|
1374
1427
|
'<label class="field-label" for="cfg-notif-sound">\u64ad\u653e\u63d0\u793a\u97f3</label>' +
|
|
1375
1428
|
'</div>' +
|
|
1376
1429
|
'<p class="hint" style="margin-top:0;margin-bottom:10px">\u91cd\u8981\u901a\u77e5\uff08\u7248\u672c\u66f4\u65b0\u3001\u6743\u9650\u7b49\u5f85\u7b49\uff09\u65f6\u64ad\u653e\u67d4\u548c\u7684\u63d0\u793a\u97f3</p>' +
|
|
1430
|
+
'<div class="field" id="notif-volume-field" style="margin-bottom:10px">' +
|
|
1431
|
+
'<label class="field-label" style="margin-bottom:4px">\u97f3\u91cf</label>' +
|
|
1432
|
+
'<div style="display:flex;align-items:center;gap:8px">' +
|
|
1433
|
+
'<input id="cfg-notif-volume" type="range" min="0" max="100" step="5" style="flex:1;accent-color:var(--accent)" />' +
|
|
1434
|
+
'<span id="cfg-notif-volume-val" style="min-width:32px;text-align:right;font-size:12px;color:var(--text-secondary)">80%</span>' +
|
|
1435
|
+
'</div>' +
|
|
1436
|
+
'</div>' +
|
|
1377
1437
|
'<div class="field field-inline">' +
|
|
1378
1438
|
'<input id="cfg-notif-bubble" type="checkbox" class="field-checkbox" />' +
|
|
1379
1439
|
'<label class="field-label" for="cfg-notif-bubble">\u5e94\u7528\u5185\u901a\u77e5\u6c14\u6ce1</label>' +
|
|
1380
1440
|
'</div>' +
|
|
1381
1441
|
'<p class="hint" style="margin-top:0;margin-bottom:10px">\u5728\u9875\u9762\u9876\u90e8\u5f39\u51fa\u6d6e\u52a8\u901a\u77e5\u6c14\u6ce1</p>' +
|
|
1442
|
+
'<div id="native-sound-section" class="settings-notification-section hidden" style="margin-top:6px">' +
|
|
1443
|
+
'<div class="settings-section-title">\u7cfb\u7edf\u901a\u77e5\u94c3\u58f0</div>' +
|
|
1444
|
+
'<div class="settings-about-row">' +
|
|
1445
|
+
'<span class="settings-label">\u94c3\u58f0</span>' +
|
|
1446
|
+
'<div style="display:flex;align-items:center;gap:6px">' +
|
|
1447
|
+
'<select id="native-sound-select" class="field-select" style="min-width:100px"></select>' +
|
|
1448
|
+
'<button id="native-sound-preview" class="btn btn-ghost btn-sm">\u25b6 \u8bd5\u542c</button>' +
|
|
1449
|
+
'</div>' +
|
|
1450
|
+
'</div>' +
|
|
1451
|
+
'<p class="hint" style="margin-top:0">\u9009\u62e9 Android \u7cfb\u7edf\u901a\u77e5\u4f7f\u7528\u7684\u94c3\u58f0</p>' +
|
|
1452
|
+
'</div>' +
|
|
1382
1453
|
'<div class="settings-notification-section" style="margin-top:6px">' +
|
|
1383
1454
|
'<div class="settings-section-title">\u6d4f\u89c8\u5668\u901a\u77e5</div>' +
|
|
1384
1455
|
'<div class="settings-about-row">' +
|
|
@@ -1389,13 +1460,18 @@
|
|
|
1389
1460
|
'<button id="notification-request-btn" class="btn btn-ghost btn-sm hidden">\u6388\u6743\u901a\u77e5</button>' +
|
|
1390
1461
|
'<button id="notification-reset-btn" class="btn btn-ghost btn-sm hidden">\u91cd\u65b0\u6388\u6743</button>' +
|
|
1391
1462
|
'<button id="notification-test-btn" class="btn btn-ghost btn-sm">\u53d1\u9001\u6d4b\u8bd5\u901a\u77e5</button>' +
|
|
1463
|
+
'<button id="notification-test-delay-btn" class="btn btn-ghost btn-sm">10 \u79d2\u540e\u53d1\u9001</button>' +
|
|
1392
1464
|
'</div>' +
|
|
1393
1465
|
'<p id="notification-test-message" class="hint hidden"></p>' +
|
|
1394
1466
|
'</div>' +
|
|
1395
1467
|
'</div>' +
|
|
1396
1468
|
|
|
1397
1469
|
// General config tab
|
|
1398
|
-
'<div class="settings-panel" id="settings-tab-general">' +
|
|
1470
|
+
'<div class="settings-panel" id="settings-tab-general" role="tabpanel">' +
|
|
1471
|
+
'<div class="settings-panel-header">' +
|
|
1472
|
+
'<h3 class="settings-panel-title">基本配置</h3>' +
|
|
1473
|
+
'<p class="settings-panel-desc">配置服务监听地址、默认模式、语言和工作目录。</p>' +
|
|
1474
|
+
'</div>' +
|
|
1399
1475
|
'<div class="field-row">' +
|
|
1400
1476
|
'<div class="field">' +
|
|
1401
1477
|
'<label class="field-label" for="cfg-host">监听地址 (host)</label>' +
|
|
@@ -1469,12 +1545,18 @@
|
|
|
1469
1545
|
'<p id="app-icon-message" class="hint hidden" style="margin-top:8px"></p>' +
|
|
1470
1546
|
'</div>'
|
|
1471
1547
|
: '') +
|
|
1472
|
-
'<
|
|
1473
|
-
|
|
1548
|
+
'<div class="settings-actions settings-actions-sticky">' +
|
|
1549
|
+
'<button id="save-config-button" class="btn btn-primary btn-block">保存配置</button>' +
|
|
1550
|
+
'</div>' +
|
|
1551
|
+
'<p id="config-message" class="hint hidden settings-status-message"></p>' +
|
|
1474
1552
|
'</div>' +
|
|
1475
1553
|
|
|
1476
1554
|
// Security tab
|
|
1477
|
-
'<div class="settings-panel" id="settings-tab-security">' +
|
|
1555
|
+
'<div class="settings-panel" id="settings-tab-security" role="tabpanel">' +
|
|
1556
|
+
'<div class="settings-panel-header">' +
|
|
1557
|
+
'<h3 class="settings-panel-title">安全</h3>' +
|
|
1558
|
+
'<p class="settings-panel-desc">管理登录密码与 SSL 证书,敏感变更请确认后再保存。</p>' +
|
|
1559
|
+
'</div>' +
|
|
1478
1560
|
'<div class="settings-card">' +
|
|
1479
1561
|
'<h3 class="settings-section-title">\ud83d\udd12 修改密码</h3>' +
|
|
1480
1562
|
'<div class="field">' +
|
|
@@ -1506,14 +1588,22 @@
|
|
|
1506
1588
|
'</div>' +
|
|
1507
1589
|
|
|
1508
1590
|
// Command presets tab
|
|
1509
|
-
'<div class="settings-panel" id="settings-tab-presets">' +
|
|
1591
|
+
'<div class="settings-panel" id="settings-tab-presets" role="tabpanel">' +
|
|
1592
|
+
'<div class="settings-panel-header">' +
|
|
1593
|
+
'<h3 class="settings-panel-title">命令预设</h3>' +
|
|
1594
|
+
'<p class="settings-panel-desc">当前命令预设从 config.json 读取,可在这里快速查看已有配置。</p>' +
|
|
1595
|
+
'</div>' +
|
|
1510
1596
|
'<div id="presets-list" class="presets-list"></div>' +
|
|
1511
1597
|
'</div>' +
|
|
1512
1598
|
|
|
1513
1599
|
// Display settings tab
|
|
1514
|
-
'<div class="settings-panel" id="settings-tab-display">' +
|
|
1600
|
+
'<div class="settings-panel" id="settings-tab-display" role="tabpanel">' +
|
|
1601
|
+
'<div class="settings-panel-header">' +
|
|
1602
|
+
'<h3 class="settings-panel-title">显示</h3>' +
|
|
1603
|
+
'<p class="settings-panel-desc">控制聊天视图里不同卡片类型的默认展开状态。</p>' +
|
|
1604
|
+
'</div>' +
|
|
1515
1605
|
'<div class="settings-section-title">卡片默认展开状态</div>' +
|
|
1516
|
-
'<p class="hint
|
|
1606
|
+
'<p class="hint settings-inline-hint">设置结构化聊天视图中各类卡片的默认展开/折叠状态。手动操作的展开状态优先于此默认设置。</p>' +
|
|
1517
1607
|
'<div class="switch-card-list">' +
|
|
1518
1608
|
'<label class="switch-card" for="cfg-card-edit">' +
|
|
1519
1609
|
'<div class="switch-card-header">' +
|
|
@@ -1556,8 +1646,10 @@
|
|
|
1556
1646
|
'<div class="switch-card-desc">连续同类工具调用的折叠组</div>' +
|
|
1557
1647
|
'</label>' +
|
|
1558
1648
|
'</div>' +
|
|
1559
|
-
'<
|
|
1560
|
-
|
|
1649
|
+
'<div class="settings-actions settings-actions-sticky">' +
|
|
1650
|
+
'<button id="save-display-button" class="btn btn-primary btn-block">保存显示设置</button>' +
|
|
1651
|
+
'</div>' +
|
|
1652
|
+
'<p id="display-message" class="hint hidden settings-status-message"></p>' +
|
|
1561
1653
|
'</div>' +
|
|
1562
1654
|
'</div>' +
|
|
1563
1655
|
'</div>' +
|
|
@@ -2773,29 +2865,46 @@
|
|
|
2773
2865
|
});
|
|
2774
2866
|
}
|
|
2775
2867
|
|
|
2868
|
+
function getCardDefault(key) {
|
|
2869
|
+
return !!(state.config && state.config.cardDefaults && state.config.cardDefaults[key]);
|
|
2870
|
+
}
|
|
2871
|
+
|
|
2872
|
+
function lazyLoadTruncatedToolContent(container, targetEl, renderContent, renderError) {
|
|
2873
|
+
if (!container || container.dataset.truncated !== "true" || container.dataset.loaded === "true") return;
|
|
2874
|
+
var toolUseId = container.dataset.toolUseId;
|
|
2875
|
+
if (!toolUseId) return;
|
|
2876
|
+
if (targetEl) targetEl.innerHTML = '<div class="tool-content-loading">加载中…</div>';
|
|
2877
|
+
container.dataset.loaded = "loading";
|
|
2878
|
+
__fetchToolContent(toolUseId, function(err, data) {
|
|
2879
|
+
if (err) {
|
|
2880
|
+
if (targetEl) targetEl.innerHTML = renderError || '<div class="tool-content-error">加载失败,点击重试</div>';
|
|
2881
|
+
container.dataset.loaded = "";
|
|
2882
|
+
return;
|
|
2883
|
+
}
|
|
2884
|
+
container.dataset.truncated = "false";
|
|
2885
|
+
container.dataset.loaded = "true";
|
|
2886
|
+
var content = typeof data.content === "string" ? data.content : JSON.stringify(data.content);
|
|
2887
|
+
renderContent(content, data);
|
|
2888
|
+
});
|
|
2889
|
+
}
|
|
2890
|
+
|
|
2776
2891
|
window.__tcToggle = function(e, headerEl) {
|
|
2777
|
-
var card = headerEl.closest(".tool-use-card");
|
|
2892
|
+
var card = headerEl.closest(".tool-use-card") || headerEl.closest(".inline-diff");
|
|
2778
2893
|
if (card) {
|
|
2779
2894
|
var wasCollapsed = card.classList.contains("collapsed");
|
|
2780
2895
|
card.classList.toggle("collapsed");
|
|
2781
|
-
|
|
2782
|
-
|
|
2783
|
-
if (wasCollapsed
|
|
2784
|
-
var toolUseId = card.dataset.toolUseId;
|
|
2896
|
+
var expandKind = card.dataset.expandKind || "tool-card";
|
|
2897
|
+
persistElementExpandState(card, expandKind);
|
|
2898
|
+
if (wasCollapsed) {
|
|
2785
2899
|
var resultDiv = card.querySelector(".tool-use-result");
|
|
2786
|
-
|
|
2787
|
-
|
|
2788
|
-
|
|
2789
|
-
|
|
2790
|
-
if (resultDiv) resultDiv.innerHTML = '<div class="tool-content-error" onclick="__tcToggle(null, card.querySelector(\'.tool-use-header\'))">加载失败,点击重试</div>';
|
|
2791
|
-
card.dataset.loaded = "";
|
|
2792
|
-
} else {
|
|
2793
|
-
card.dataset.truncated = "false";
|
|
2794
|
-
card.dataset.loaded = "true";
|
|
2795
|
-
var content = typeof data.content === "string" ? data.content : JSON.stringify(data.content);
|
|
2900
|
+
lazyLoadTruncatedToolContent(
|
|
2901
|
+
card,
|
|
2902
|
+
resultDiv,
|
|
2903
|
+
function(content) {
|
|
2796
2904
|
if (resultDiv) resultDiv.innerHTML = '<pre class="tool-use-result-content">' + escapeHtml(content) + '</pre>';
|
|
2797
|
-
}
|
|
2798
|
-
|
|
2905
|
+
},
|
|
2906
|
+
'<div class="tool-content-error" onclick="__tcToggle(null, this.closest(\'.tool-use-card,.inline-diff\').querySelector(\'.tool-use-header,.diff-header\'))">加载失败,点击重试</div>'
|
|
2907
|
+
);
|
|
2799
2908
|
}
|
|
2800
2909
|
}
|
|
2801
2910
|
if (e) { e.preventDefault(); e.stopPropagation(); }
|
|
@@ -2835,22 +2944,10 @@
|
|
|
2835
2944
|
statusSpan.textContent = "✓";
|
|
2836
2945
|
}
|
|
2837
2946
|
}
|
|
2838
|
-
|
|
2839
|
-
|
|
2840
|
-
|
|
2841
|
-
|
|
2842
|
-
el.dataset.loaded = "loading";
|
|
2843
|
-
__fetchToolContent(toolUseId, function(err, data) {
|
|
2844
|
-
if (err) {
|
|
2845
|
-
if (body) body.innerHTML = '<div class="tool-content-error">加载失败,点击重试</div>';
|
|
2846
|
-
el.dataset.loaded = "";
|
|
2847
|
-
} else {
|
|
2848
|
-
el.dataset.truncated = "false";
|
|
2849
|
-
el.dataset.loaded = "true";
|
|
2850
|
-
var content = typeof data.content === "string" ? data.content : JSON.stringify(data.content);
|
|
2851
|
-
el.dataset.result = content;
|
|
2852
|
-
if (body) body.innerHTML = '<div class="inline-tool-result">' + formatInlineResult(content, "") + '</div>';
|
|
2853
|
-
}
|
|
2947
|
+
if (expanded) {
|
|
2948
|
+
lazyLoadTruncatedToolContent(el, body, function(content) {
|
|
2949
|
+
el.dataset.result = content;
|
|
2950
|
+
if (body) body.innerHTML = '<div class="inline-tool-result">' + formatInlineResult(content, "") + '</div>';
|
|
2854
2951
|
});
|
|
2855
2952
|
}
|
|
2856
2953
|
persistElementExpandState(el, "inline-tool");
|
|
@@ -2867,29 +2964,17 @@
|
|
|
2867
2964
|
var toggleIcon = el.querySelector(".term-toggle-icon");
|
|
2868
2965
|
if (toggleIcon) toggleIcon.textContent = isHidden ? "▼" : "▶";
|
|
2869
2966
|
persistElementExpandState(container, "terminal");
|
|
2870
|
-
|
|
2871
|
-
if (isHidden && container.dataset.truncated === "true" && container.dataset.loaded !== "true") {
|
|
2872
|
-
var toolUseId = container.dataset.toolUseId;
|
|
2967
|
+
if (isHidden) {
|
|
2873
2968
|
var termOutput = body.querySelector(".term-output");
|
|
2874
|
-
|
|
2875
|
-
|
|
2876
|
-
|
|
2877
|
-
|
|
2878
|
-
|
|
2879
|
-
|
|
2880
|
-
|
|
2881
|
-
container.dataset.truncated = "false";
|
|
2882
|
-
container.dataset.loaded = "true";
|
|
2883
|
-
var content = typeof data.content === "string" ? data.content : JSON.stringify(data.content);
|
|
2884
|
-
if (termOutput) {
|
|
2885
|
-
var lines = content.split("\n");
|
|
2886
|
-
var html = "";
|
|
2887
|
-
for (var i = 0; i < lines.length; i++) {
|
|
2888
|
-
if (!lines[i] && i === lines.length - 1) continue;
|
|
2889
|
-
html += '<div class="term-line">' + escapeHtml(lines[i]) + '</div>';
|
|
2890
|
-
}
|
|
2891
|
-
termOutput.innerHTML = html;
|
|
2969
|
+
lazyLoadTruncatedToolContent(container, termOutput, function(content) {
|
|
2970
|
+
if (termOutput) {
|
|
2971
|
+
var lines = content.split("\n");
|
|
2972
|
+
var html = "";
|
|
2973
|
+
for (var i = 0; i < lines.length; i++) {
|
|
2974
|
+
if (!lines[i] && i === lines.length - 1) continue;
|
|
2975
|
+
html += '<div class="term-line">' + escapeHtml(lines[i]) + '</div>';
|
|
2892
2976
|
}
|
|
2977
|
+
termOutput.innerHTML = html;
|
|
2893
2978
|
}
|
|
2894
2979
|
});
|
|
2895
2980
|
}
|
|
@@ -3130,6 +3215,8 @@
|
|
|
3130
3215
|
if (drawerBackdrop) drawerBackdrop.addEventListener("click", closeSessionsDrawer);
|
|
3131
3216
|
var closeDrawerBtn = document.getElementById("close-drawer-button");
|
|
3132
3217
|
if (closeDrawerBtn) closeDrawerBtn.addEventListener("click", closeSessionsDrawer);
|
|
3218
|
+
var pinBtn = document.getElementById("sidebar-pin-btn");
|
|
3219
|
+
if (pinBtn) pinBtn.addEventListener("click", toggleSidebarPin);
|
|
3133
3220
|
var homeBtn = document.getElementById("sidebar-home-btn");
|
|
3134
3221
|
if (homeBtn) homeBtn.addEventListener("click", function() {
|
|
3135
3222
|
state.selectedId = null;
|
|
@@ -3209,6 +3296,33 @@
|
|
|
3209
3296
|
try { localStorage.setItem("wand-notif-sound", String(state.notifSound)); } catch (e) {}
|
|
3210
3297
|
// Preview sound when toggling on
|
|
3211
3298
|
if (state.notifSound) _doPlaySound();
|
|
3299
|
+
// Toggle volume slider visibility
|
|
3300
|
+
var volField = document.getElementById("notif-volume-field");
|
|
3301
|
+
if (volField) volField.style.display = state.notifSound ? "" : "none";
|
|
3302
|
+
});
|
|
3303
|
+
}
|
|
3304
|
+
// Volume slider
|
|
3305
|
+
var notifVolumeEl = document.getElementById("cfg-notif-volume");
|
|
3306
|
+
var notifVolumeVal = document.getElementById("cfg-notif-volume-val");
|
|
3307
|
+
if (notifVolumeEl) {
|
|
3308
|
+
notifVolumeEl.value = state.notifVolume;
|
|
3309
|
+
if (notifVolumeVal) notifVolumeVal.textContent = state.notifVolume + "%";
|
|
3310
|
+
// Hide if sound is off
|
|
3311
|
+
var volField = document.getElementById("notif-volume-field");
|
|
3312
|
+
if (volField) volField.style.display = state.notifSound ? "" : "none";
|
|
3313
|
+
var _volDebounce = null;
|
|
3314
|
+
notifVolumeEl.addEventListener("input", function() {
|
|
3315
|
+
state.notifVolume = parseInt(notifVolumeEl.value, 10);
|
|
3316
|
+
if (notifVolumeVal) notifVolumeVal.textContent = state.notifVolume + "%";
|
|
3317
|
+
try { localStorage.setItem("wand-notif-volume", String(state.notifVolume)); } catch (e) {}
|
|
3318
|
+
// Also sync to native bridge if available
|
|
3319
|
+
if (_hasNativeBridge && typeof WandNative.setNotificationVolume === "function") {
|
|
3320
|
+
try { WandNative.setNotificationVolume(state.notifVolume); } catch (_e) {}
|
|
3321
|
+
}
|
|
3322
|
+
});
|
|
3323
|
+
// Preview on release
|
|
3324
|
+
notifVolumeEl.addEventListener("change", function() {
|
|
3325
|
+
_doPlaySound();
|
|
3212
3326
|
});
|
|
3213
3327
|
}
|
|
3214
3328
|
var notifBubbleEl = document.getElementById("cfg-notif-bubble");
|
|
@@ -3236,7 +3350,38 @@
|
|
|
3236
3350
|
if (notifResetBtn) notifResetBtn.addEventListener("click", resetNotificationPermission);
|
|
3237
3351
|
var notifTestBtn = document.getElementById("notification-test-btn");
|
|
3238
3352
|
if (notifTestBtn) notifTestBtn.addEventListener("click", testNotification);
|
|
3353
|
+
var notifTestDelayBtn = document.getElementById("notification-test-delay-btn");
|
|
3354
|
+
if (notifTestDelayBtn) notifTestDelayBtn.addEventListener("click", scheduleTestNotification);
|
|
3239
3355
|
updateNotificationStatus();
|
|
3356
|
+
// Native notification sound selector (APK only)
|
|
3357
|
+
if (_hasNativeBridge && typeof WandNative.getAvailableSounds === "function") {
|
|
3358
|
+
var nativeSoundSection = document.getElementById("native-sound-section");
|
|
3359
|
+
var nativeSoundSelect = document.getElementById("native-sound-select");
|
|
3360
|
+
var nativeSoundPreview = document.getElementById("native-sound-preview");
|
|
3361
|
+
if (nativeSoundSection && nativeSoundSelect) {
|
|
3362
|
+
nativeSoundSection.classList.remove("hidden");
|
|
3363
|
+
try {
|
|
3364
|
+
var sounds = JSON.parse(WandNative.getAvailableSounds());
|
|
3365
|
+
var current = WandNative.getNotificationSound();
|
|
3366
|
+
nativeSoundSelect.innerHTML = "";
|
|
3367
|
+
for (var si = 0; si < sounds.length; si++) {
|
|
3368
|
+
var opt = document.createElement("option");
|
|
3369
|
+
opt.value = sounds[si].id;
|
|
3370
|
+
opt.textContent = sounds[si].name;
|
|
3371
|
+
if (sounds[si].id === current) opt.selected = true;
|
|
3372
|
+
nativeSoundSelect.appendChild(opt);
|
|
3373
|
+
}
|
|
3374
|
+
nativeSoundSelect.addEventListener("change", function() {
|
|
3375
|
+
try { WandNative.setNotificationSound(nativeSoundSelect.value); } catch (_e) {}
|
|
3376
|
+
});
|
|
3377
|
+
if (nativeSoundPreview) {
|
|
3378
|
+
nativeSoundPreview.addEventListener("click", function() {
|
|
3379
|
+
try { WandNative.previewSound(nativeSoundSelect.value); } catch (_e) {}
|
|
3380
|
+
});
|
|
3381
|
+
}
|
|
3382
|
+
} catch (_e) {}
|
|
3383
|
+
}
|
|
3384
|
+
}
|
|
3240
3385
|
var newSessBtn = document.getElementById("topbar-new-session-button");
|
|
3241
3386
|
if (newSessBtn) newSessBtn.addEventListener("click", openSessionModal);
|
|
3242
3387
|
var drawerNewSessBtn = document.getElementById("drawer-new-session-button");
|
|
@@ -3803,7 +3948,9 @@
|
|
|
3803
3948
|
} else {
|
|
3804
3949
|
selectSession(sessionId);
|
|
3805
3950
|
}
|
|
3806
|
-
|
|
3951
|
+
if (!state.sidebarPinned || isMobileLayout()) {
|
|
3952
|
+
closeSessionsDrawer();
|
|
3953
|
+
}
|
|
3807
3954
|
}
|
|
3808
3955
|
|
|
3809
3956
|
function handleSessionItemClick(event) {
|
|
@@ -4308,6 +4455,12 @@
|
|
|
4308
4455
|
} else {
|
|
4309
4456
|
updateTerminalJumpToBottomButton();
|
|
4310
4457
|
}
|
|
4458
|
+
// When switching sessions, re-fit the terminal so the PTY receives
|
|
4459
|
+
// the correct dimensions for this client's viewport.
|
|
4460
|
+
if (sessionChanged && state.fitAddon) {
|
|
4461
|
+
state.terminalViewportSize = { width: 0, height: 0 };
|
|
4462
|
+
scheduleTerminalResize(true);
|
|
4463
|
+
}
|
|
4311
4464
|
return wrote || sessionChanged;
|
|
4312
4465
|
}
|
|
4313
4466
|
|
|
@@ -4382,6 +4535,26 @@
|
|
|
4382
4535
|
state.fitAddon = fitAddonConstructor ? new fitAddonConstructor() : null;
|
|
4383
4536
|
if (state.fitAddon) {
|
|
4384
4537
|
state.terminal.loadAddon(state.fitAddon);
|
|
4538
|
+
// Patch: FitAddon subtracts 14px for a scrollbar that CSS hides;
|
|
4539
|
+
// recalculate cols without the scrollbar deduction.
|
|
4540
|
+
var _origPropose = state.fitAddon.proposeDimensions;
|
|
4541
|
+
state.fitAddon.proposeDimensions = function() {
|
|
4542
|
+
var result = _origPropose.call(state.fitAddon);
|
|
4543
|
+
if (result && state.terminal) {
|
|
4544
|
+
try {
|
|
4545
|
+
var core = state.terminal._core;
|
|
4546
|
+
var cellW = core._renderService.dimensions.css.cell.width;
|
|
4547
|
+
var el = state.terminal.element;
|
|
4548
|
+
if (cellW > 0 && el && el.parentElement) {
|
|
4549
|
+
var pw = Math.max(0, parseInt(window.getComputedStyle(el.parentElement).getPropertyValue("width")));
|
|
4550
|
+
var es = window.getComputedStyle(el);
|
|
4551
|
+
var ePad = parseInt(es.getPropertyValue("padding-left")) + parseInt(es.getPropertyValue("padding-right"));
|
|
4552
|
+
result.cols = Math.max(2, Math.floor((pw - ePad) / cellW));
|
|
4553
|
+
}
|
|
4554
|
+
} catch(e) {}
|
|
4555
|
+
}
|
|
4556
|
+
return result;
|
|
4557
|
+
};
|
|
4385
4558
|
} else {
|
|
4386
4559
|
console.error("[wand] xterm fit addon failed to load; continuing without fit support.");
|
|
4387
4560
|
}
|
|
@@ -4400,6 +4573,24 @@
|
|
|
4400
4573
|
// Retry-based fit: wait for browser to complete layout before measuring and fitting
|
|
4401
4574
|
if (state.fitAddon) {
|
|
4402
4575
|
ensureTerminalFit();
|
|
4576
|
+
// Secondary fit after fonts are loaded — FitAddon measures character
|
|
4577
|
+
// dimensions from the rendered font; if a custom web font (e.g. Geist
|
|
4578
|
+
// Mono) hasn't loaded yet the initial fit() uses fallback metrics and
|
|
4579
|
+
// computes too few columns.
|
|
4580
|
+
if (document.fonts && document.fonts.ready) {
|
|
4581
|
+
document.fonts.ready.then(function() {
|
|
4582
|
+
state.terminalViewportSize = { width: 0, height: 0 };
|
|
4583
|
+
ensureTerminalFit();
|
|
4584
|
+
});
|
|
4585
|
+
}
|
|
4586
|
+
// Safety-net fit after layout has fully stabilised (CSS transitions,
|
|
4587
|
+
// deferred reflows, late font loads, etc.)
|
|
4588
|
+
setTimeout(function() {
|
|
4589
|
+
if (state.terminal && state.fitAddon) {
|
|
4590
|
+
state.terminalViewportSize = { width: 0, height: 0 };
|
|
4591
|
+
ensureTerminalFit();
|
|
4592
|
+
}
|
|
4593
|
+
}, 500);
|
|
4403
4594
|
}
|
|
4404
4595
|
|
|
4405
4596
|
var viewport = getTerminalViewport();
|
|
@@ -5139,7 +5330,6 @@
|
|
|
5139
5330
|
}
|
|
5140
5331
|
state.selectedId = id;
|
|
5141
5332
|
persistSelectedId();
|
|
5142
|
-
// Clear tool content cache on session switch
|
|
5143
5333
|
state.toolContentCache = {};
|
|
5144
5334
|
// Clear queued inputs from the previous session to prevent cross-session leaks
|
|
5145
5335
|
state.messageQueue = [];
|
|
@@ -5170,6 +5360,22 @@
|
|
|
5170
5360
|
subscribeToSession(id);
|
|
5171
5361
|
}
|
|
5172
5362
|
|
|
5363
|
+
function updatePinState() {
|
|
5364
|
+
var drawer = document.getElementById("sessions-drawer");
|
|
5365
|
+
var mainLayout = document.querySelector(".main-layout");
|
|
5366
|
+
var pinBtn = document.getElementById("sidebar-pin-btn");
|
|
5367
|
+
if (drawer) {
|
|
5368
|
+
drawer.classList.toggle("pinned", state.sidebarPinned && !isMobileLayout());
|
|
5369
|
+
}
|
|
5370
|
+
if (mainLayout) {
|
|
5371
|
+
mainLayout.classList.toggle("sidebar-pinned", state.sidebarPinned && !isMobileLayout());
|
|
5372
|
+
}
|
|
5373
|
+
if (pinBtn) {
|
|
5374
|
+
pinBtn.classList.toggle("pinned", state.sidebarPinned);
|
|
5375
|
+
pinBtn.title = state.sidebarPinned ? "取消固定侧栏" : "固定侧栏";
|
|
5376
|
+
}
|
|
5377
|
+
}
|
|
5378
|
+
|
|
5173
5379
|
function updateDrawerState() {
|
|
5174
5380
|
var drawer = document.getElementById("sessions-drawer");
|
|
5175
5381
|
var backdrop = document.getElementById("sessions-drawer-backdrop");
|
|
@@ -5187,9 +5393,11 @@
|
|
|
5187
5393
|
if (toggleBtn) {
|
|
5188
5394
|
toggleBtn.classList.toggle("active", state.sessionsDrawerOpen);
|
|
5189
5395
|
}
|
|
5396
|
+
updatePinState();
|
|
5190
5397
|
}
|
|
5191
5398
|
|
|
5192
5399
|
function toggleSessionsDrawer() {
|
|
5400
|
+
if (state.sidebarPinned && !isMobileLayout()) return;
|
|
5193
5401
|
state.sessionsDrawerOpen = !state.sessionsDrawerOpen;
|
|
5194
5402
|
if (state.sessionsDrawerOpen && isMobileLayout()) {
|
|
5195
5403
|
state.filePanelOpen = false;
|
|
@@ -5201,12 +5409,38 @@
|
|
|
5201
5409
|
}
|
|
5202
5410
|
|
|
5203
5411
|
function closeSessionsDrawer() {
|
|
5412
|
+
if (state.sidebarPinned && !isMobileLayout()) return;
|
|
5204
5413
|
if (!state.sessionsDrawerOpen) return;
|
|
5205
5414
|
closeSwipedItem();
|
|
5206
5415
|
state.sessionsDrawerOpen = false;
|
|
5207
5416
|
updateLayoutState();
|
|
5208
5417
|
}
|
|
5209
5418
|
|
|
5419
|
+
function toggleSidebarPin() {
|
|
5420
|
+
if (isMobileLayout()) return;
|
|
5421
|
+
state.sidebarPinned = !state.sidebarPinned;
|
|
5422
|
+
try {
|
|
5423
|
+
localStorage.setItem("wand-sidebar-pinned", String(state.sidebarPinned));
|
|
5424
|
+
} catch (e) {}
|
|
5425
|
+
if (state.sidebarPinned) {
|
|
5426
|
+
state.sessionsDrawerOpen = true;
|
|
5427
|
+
}
|
|
5428
|
+
updateLayoutState();
|
|
5429
|
+
// Refit terminal after padding-left transition completes
|
|
5430
|
+
var mainLayout = document.querySelector(".main-layout");
|
|
5431
|
+
if (mainLayout) {
|
|
5432
|
+
var onEnd = function(e) {
|
|
5433
|
+
if (e.propertyName === "padding-left") {
|
|
5434
|
+
mainLayout.removeEventListener("transitionend", onEnd);
|
|
5435
|
+
scheduleTerminalResize(true);
|
|
5436
|
+
}
|
|
5437
|
+
};
|
|
5438
|
+
mainLayout.addEventListener("transitionend", onEnd);
|
|
5439
|
+
}
|
|
5440
|
+
// Fallback refit in case transition doesn't fire
|
|
5441
|
+
setTimeout(function() { scheduleTerminalResize(true); }, 350);
|
|
5442
|
+
}
|
|
5443
|
+
|
|
5210
5444
|
// Store last focused element for focus trap
|
|
5211
5445
|
var lastFocusedElement = null;
|
|
5212
5446
|
var focusTrapHandler = null;
|
|
@@ -5458,6 +5692,7 @@
|
|
|
5458
5692
|
if (confirmEl) confirmEl.value = "";
|
|
5459
5693
|
hideSettingsMessages();
|
|
5460
5694
|
setupFocusTrap(modal);
|
|
5695
|
+
bindSettingsTabKeyboardNavigation();
|
|
5461
5696
|
// Activate first tab
|
|
5462
5697
|
switchSettingsTab("about");
|
|
5463
5698
|
// Load settings data
|
|
@@ -5467,11 +5702,35 @@
|
|
|
5467
5702
|
var bubbleEl = document.getElementById("cfg-notif-bubble");
|
|
5468
5703
|
if (soundEl) soundEl.checked = state.notifSound;
|
|
5469
5704
|
if (bubbleEl) bubbleEl.checked = state.notifBubble;
|
|
5705
|
+
var volEl = document.getElementById("cfg-notif-volume");
|
|
5706
|
+
var volValEl = document.getElementById("cfg-notif-volume-val");
|
|
5707
|
+
if (volEl) {
|
|
5708
|
+
volEl.value = state.notifVolume;
|
|
5709
|
+
if (volValEl) volValEl.textContent = state.notifVolume + "%";
|
|
5710
|
+
}
|
|
5711
|
+
var volField = document.getElementById("notif-volume-field");
|
|
5712
|
+
if (volField) volField.style.display = state.notifSound ? "" : "none";
|
|
5470
5713
|
updateNotificationStatus();
|
|
5471
5714
|
// Load current app icon selection (APK only)
|
|
5472
5715
|
if (typeof WandNative !== "undefined" && typeof WandNative.getAppIcon === "function") {
|
|
5473
5716
|
try { _updateAppIconSelection(WandNative.getAppIcon() || "shorthair"); } catch (_e) {}
|
|
5474
5717
|
}
|
|
5718
|
+
// Sync native notification sound selector and volume (APK only)
|
|
5719
|
+
if (_hasNativeBridge && typeof WandNative.getNotificationSound === "function") {
|
|
5720
|
+
try {
|
|
5721
|
+
var nsSel = document.getElementById("native-sound-select");
|
|
5722
|
+
if (nsSel) nsSel.value = WandNative.getNotificationSound();
|
|
5723
|
+
} catch (_e) {}
|
|
5724
|
+
}
|
|
5725
|
+
if (_hasNativeBridge && typeof WandNative.getNotificationVolume === "function") {
|
|
5726
|
+
try {
|
|
5727
|
+
var nativeVol = WandNative.getNotificationVolume();
|
|
5728
|
+
state.notifVolume = nativeVol;
|
|
5729
|
+
if (volEl) volEl.value = nativeVol;
|
|
5730
|
+
if (volValEl) volValEl.textContent = nativeVol + "%";
|
|
5731
|
+
try { localStorage.setItem("wand-notif-volume", String(nativeVol)); } catch (_e) {}
|
|
5732
|
+
} catch (_e) {}
|
|
5733
|
+
}
|
|
5475
5734
|
}
|
|
5476
5735
|
}
|
|
5477
5736
|
|
|
@@ -5548,21 +5807,76 @@
|
|
|
5548
5807
|
var tabs = document.querySelectorAll(".settings-tab");
|
|
5549
5808
|
var panels = document.querySelectorAll(".settings-panel");
|
|
5550
5809
|
for (var i = 0; i < tabs.length; i++) {
|
|
5551
|
-
|
|
5810
|
+
var isActive = tabs[i].getAttribute("data-tab") === tabName;
|
|
5811
|
+
if (isActive) {
|
|
5552
5812
|
tabs[i].classList.add("active");
|
|
5553
5813
|
} else {
|
|
5554
5814
|
tabs[i].classList.remove("active");
|
|
5555
5815
|
}
|
|
5816
|
+
tabs[i].setAttribute("aria-selected", isActive ? "true" : "false");
|
|
5817
|
+
tabs[i].setAttribute("tabindex", isActive ? "0" : "-1");
|
|
5556
5818
|
}
|
|
5557
5819
|
for (var j = 0; j < panels.length; j++) {
|
|
5558
|
-
|
|
5820
|
+
var isPanelActive = panels[j].id === "settings-tab-" + tabName;
|
|
5821
|
+
if (isPanelActive) {
|
|
5559
5822
|
panels[j].classList.add("active");
|
|
5823
|
+
panels[j].removeAttribute("hidden");
|
|
5560
5824
|
} else {
|
|
5561
5825
|
panels[j].classList.remove("active");
|
|
5826
|
+
panels[j].setAttribute("hidden", "hidden");
|
|
5562
5827
|
}
|
|
5563
5828
|
}
|
|
5564
5829
|
}
|
|
5565
5830
|
|
|
5831
|
+
function handleSettingsTabKeydown(event) {
|
|
5832
|
+
if (!event) return;
|
|
5833
|
+
if (event.key !== "ArrowUp" && event.key !== "ArrowDown" && event.key !== "Home" && event.key !== "End") {
|
|
5834
|
+
return;
|
|
5835
|
+
}
|
|
5836
|
+
var tabs = Array.prototype.slice.call(document.querySelectorAll(".settings-tab"));
|
|
5837
|
+
if (!tabs.length) return;
|
|
5838
|
+
var currentIndex = tabs.indexOf(event.currentTarget);
|
|
5839
|
+
if (currentIndex === -1) return;
|
|
5840
|
+
event.preventDefault();
|
|
5841
|
+
var nextIndex = currentIndex;
|
|
5842
|
+
if (event.key === "ArrowUp") nextIndex = currentIndex > 0 ? currentIndex - 1 : tabs.length - 1;
|
|
5843
|
+
if (event.key === "ArrowDown") nextIndex = currentIndex < tabs.length - 1 ? currentIndex + 1 : 0;
|
|
5844
|
+
if (event.key === "Home") nextIndex = 0;
|
|
5845
|
+
if (event.key === "End") nextIndex = tabs.length - 1;
|
|
5846
|
+
var nextTab = tabs[nextIndex];
|
|
5847
|
+
if (!nextTab) return;
|
|
5848
|
+
var nextName = nextTab.getAttribute("data-tab");
|
|
5849
|
+
if (nextName) switchSettingsTab(nextName);
|
|
5850
|
+
if (typeof nextTab.focus === "function") nextTab.focus();
|
|
5851
|
+
}
|
|
5852
|
+
|
|
5853
|
+
function bindSettingsTabKeyboardNavigation() {
|
|
5854
|
+
var tabs = document.querySelectorAll(".settings-tab");
|
|
5855
|
+
for (var i = 0; i < tabs.length; i++) {
|
|
5856
|
+
tabs[i].removeEventListener("keydown", handleSettingsTabKeydown);
|
|
5857
|
+
tabs[i].addEventListener("keydown", handleSettingsTabKeydown);
|
|
5858
|
+
}
|
|
5859
|
+
}
|
|
5860
|
+
|
|
5861
|
+
function updateSettingsSidebarStatus(data) {
|
|
5862
|
+
if (!data) return;
|
|
5863
|
+
var cfg = data.config || {};
|
|
5864
|
+
var metaMap = {
|
|
5865
|
+
about: data.version ? ("当前 v" + data.version) : "版本与更新信息",
|
|
5866
|
+
general: [cfg.defaultMode || "default", cfg.language || "自动语言"].filter(Boolean).join(" · "),
|
|
5867
|
+
notifications: state.notifSound ? ("提示音 " + state.notifVolume + "%") : "提示音已关闭",
|
|
5868
|
+
security: data.hasCert ? "已安装 SSL 证书" : "密码与证书管理",
|
|
5869
|
+
presets: cfg.commandPresets && cfg.commandPresets.length ? (cfg.commandPresets.length + " 条预设") : "暂无预设",
|
|
5870
|
+
display: "控制卡片默认展开"
|
|
5871
|
+
};
|
|
5872
|
+
for (var key in metaMap) {
|
|
5873
|
+
if (!Object.prototype.hasOwnProperty.call(metaMap, key)) continue;
|
|
5874
|
+
var tab = document.querySelector('.settings-tab[data-tab="' + key + '"] .settings-tab-meta');
|
|
5875
|
+
if (tab) tab.textContent = metaMap[key] || "";
|
|
5876
|
+
}
|
|
5877
|
+
}
|
|
5878
|
+
|
|
5879
|
+
|
|
5566
5880
|
function copyToClipboard(text, triggerBtn) {
|
|
5567
5881
|
if (!text) return;
|
|
5568
5882
|
navigator.clipboard.writeText(text).then(function() {
|
|
@@ -5607,6 +5921,7 @@
|
|
|
5607
5921
|
fetch("/api/settings", { credentials: "same-origin" })
|
|
5608
5922
|
.then(function(res) { return res.json(); })
|
|
5609
5923
|
.then(function(data) {
|
|
5924
|
+
updateSettingsSidebarStatus(data);
|
|
5610
5925
|
// About
|
|
5611
5926
|
var nameEl = document.getElementById("settings-pkg-name");
|
|
5612
5927
|
var verEl = document.getElementById("settings-version");
|
|
@@ -5691,7 +6006,7 @@
|
|
|
5691
6006
|
apkMessageEl.classList.remove("hidden");
|
|
5692
6007
|
}
|
|
5693
6008
|
} else {
|
|
5694
|
-
// ──
|
|
6009
|
+
// ── 浏览器模式:显示线上版本 + 本地版本 + 下载按钮 ──
|
|
5695
6010
|
if (androidApk.github && apkGithubRow && apkGithubEl) {
|
|
5696
6011
|
var ghLabel2 = androidApk.github.version ? ("v" + androidApk.github.version) : androidApk.github.fileName;
|
|
5697
6012
|
if (typeof androidApk.github.size === "number") ghLabel2 += " · " + formatBytes(androidApk.github.size);
|
|
@@ -5704,7 +6019,22 @@
|
|
|
5704
6019
|
window.open(androidApk.github.downloadUrl, "_blank");
|
|
5705
6020
|
};
|
|
5706
6021
|
}
|
|
5707
|
-
}
|
|
6022
|
+
}
|
|
6023
|
+
// 本地版本
|
|
6024
|
+
if (androidApk.local && apkLocalRow && apkLocalEl) {
|
|
6025
|
+
var lcLabel2 = androidApk.local.version ? ("v" + androidApk.local.version) : androidApk.local.fileName;
|
|
6026
|
+
if (typeof androidApk.local.size === "number") lcLabel2 += " · " + formatBytes(androidApk.local.size);
|
|
6027
|
+
apkLocalEl.textContent = lcLabel2;
|
|
6028
|
+
apkLocalRow.classList.remove("hidden");
|
|
6029
|
+
if (apkLocalBtn) {
|
|
6030
|
+
apkLocalBtn.textContent = "下载";
|
|
6031
|
+
apkLocalBtn.classList.remove("hidden");
|
|
6032
|
+
apkLocalBtn.onclick = function() {
|
|
6033
|
+
window.open(androidApk.local.downloadUrl, "_self");
|
|
6034
|
+
};
|
|
6035
|
+
}
|
|
6036
|
+
}
|
|
6037
|
+
if (!androidApk.github && !androidApk.local && apkMessageEl) {
|
|
5708
6038
|
apkMessageEl.textContent = "暂未提供";
|
|
5709
6039
|
apkMessageEl.classList.remove("hidden");
|
|
5710
6040
|
}
|
|
@@ -6110,9 +6440,44 @@
|
|
|
6110
6440
|
});
|
|
6111
6441
|
}
|
|
6112
6442
|
|
|
6443
|
+
function resetDelayedNotificationButton() {
|
|
6444
|
+
var delayBtn = document.getElementById("notification-test-delay-btn");
|
|
6445
|
+
if (!delayBtn) return;
|
|
6446
|
+
delayBtn.disabled = false;
|
|
6447
|
+
delayBtn.textContent = "10 秒后发送";
|
|
6448
|
+
}
|
|
6449
|
+
|
|
6450
|
+
function scheduleTestNotification() {
|
|
6451
|
+
var testMsgEl = document.getElementById("notification-test-message");
|
|
6452
|
+
if (state.delayedNotificationTimer) {
|
|
6453
|
+
clearTimeout(state.delayedNotificationTimer);
|
|
6454
|
+
state.delayedNotificationTimer = null;
|
|
6455
|
+
}
|
|
6456
|
+
var delayBtn = document.getElementById("notification-test-delay-btn");
|
|
6457
|
+
if (delayBtn) {
|
|
6458
|
+
delayBtn.disabled = true;
|
|
6459
|
+
delayBtn.textContent = "已安排(10s)";
|
|
6460
|
+
}
|
|
6461
|
+
if (testMsgEl) {
|
|
6462
|
+
testMsgEl.innerHTML = "已安排 10 秒后发送测试通知,请切到后台等待。";
|
|
6463
|
+
testMsgEl.style.color = "var(--text-secondary)";
|
|
6464
|
+
testMsgEl.classList.remove("hidden");
|
|
6465
|
+
}
|
|
6466
|
+
state.delayedNotificationTimer = setTimeout(function() {
|
|
6467
|
+
state.delayedNotificationTimer = null;
|
|
6468
|
+
resetDelayedNotificationButton();
|
|
6469
|
+
testNotification();
|
|
6470
|
+
}, 10000);
|
|
6471
|
+
}
|
|
6472
|
+
|
|
6113
6473
|
function testNotification() {
|
|
6114
6474
|
var testMsgEl = document.getElementById("notification-test-message");
|
|
6115
6475
|
var results = [];
|
|
6476
|
+
if (state.delayedNotificationTimer) {
|
|
6477
|
+
clearTimeout(state.delayedNotificationTimer);
|
|
6478
|
+
state.delayedNotificationTimer = null;
|
|
6479
|
+
resetDelayedNotificationButton();
|
|
6480
|
+
}
|
|
6116
6481
|
|
|
6117
6482
|
// 1. Test sound playback
|
|
6118
6483
|
var soundOk = tryPlayNotificationSound();
|
|
@@ -9295,7 +9660,22 @@
|
|
|
9295
9660
|
state.resizeObserver = new ResizeObserver(function() { scheduleTerminalResize(true); });
|
|
9296
9661
|
state.resizeObserver.observe(output);
|
|
9297
9662
|
}
|
|
9298
|
-
|
|
9663
|
+
var lastKnownDesktop = !isMobileLayout();
|
|
9664
|
+
state.resizeHandler = function() {
|
|
9665
|
+
scheduleTerminalResize(true);
|
|
9666
|
+
// Handle sidebar pin state across mobile/desktop breakpoint
|
|
9667
|
+
var isDesktop = !isMobileLayout();
|
|
9668
|
+
if (lastKnownDesktop !== isDesktop) {
|
|
9669
|
+
lastKnownDesktop = isDesktop;
|
|
9670
|
+
if (!isDesktop && state.sidebarPinned && state.sessionsDrawerOpen) {
|
|
9671
|
+
state.sessionsDrawerOpen = false;
|
|
9672
|
+
updateDrawerState();
|
|
9673
|
+
} else if (isDesktop && state.sidebarPinned && !state.sessionsDrawerOpen) {
|
|
9674
|
+
state.sessionsDrawerOpen = true;
|
|
9675
|
+
updateDrawerState();
|
|
9676
|
+
}
|
|
9677
|
+
}
|
|
9678
|
+
};
|
|
9299
9679
|
window.addEventListener("resize", state.resizeHandler);
|
|
9300
9680
|
// Also listen to visualViewport resize for pinch-zoom / browser zoom
|
|
9301
9681
|
if (window.visualViewport) {
|
|
@@ -9365,32 +9745,53 @@
|
|
|
9365
9745
|
updateTerminalJumpToBottomButton();
|
|
9366
9746
|
}
|
|
9367
9747
|
|
|
9748
|
+
function sendTerminalResize(cols, rows) {
|
|
9749
|
+
if (!state.selectedId) return;
|
|
9750
|
+
var selectedSess = state.sessions.find(function(s) { return s.id === state.selectedId; });
|
|
9751
|
+
if (isStructuredSession(selectedSess)) return;
|
|
9752
|
+
var nextSize = { cols: cols, rows: rows };
|
|
9753
|
+
if (state.lastResize.cols !== nextSize.cols || state.lastResize.rows !== nextSize.rows) {
|
|
9754
|
+
state.lastResize = nextSize;
|
|
9755
|
+
fetch("/api/sessions/" + state.selectedId + "/resize", {
|
|
9756
|
+
method: "POST",
|
|
9757
|
+
headers: { "Content-Type": "application/json" },
|
|
9758
|
+
credentials: "same-origin",
|
|
9759
|
+
body: JSON.stringify(nextSize)
|
|
9760
|
+
}).catch(function() {});
|
|
9761
|
+
}
|
|
9762
|
+
}
|
|
9763
|
+
|
|
9368
9764
|
function ensureTerminalFit() {
|
|
9369
|
-
|
|
9765
|
+
if (state.terminalFitInProgress) return;
|
|
9766
|
+
state.terminalFitInProgress = true;
|
|
9767
|
+
var maxAttempts = 20;
|
|
9370
9768
|
var attempt = 0;
|
|
9769
|
+
function finishFit() {
|
|
9770
|
+
state.terminalFitInProgress = false;
|
|
9771
|
+
}
|
|
9371
9772
|
function tryFit() {
|
|
9372
9773
|
attempt++;
|
|
9373
9774
|
state.terminalViewportSize = { width: 0, height: 0 };
|
|
9374
9775
|
if (shouldResizeTerminalViewport() && state.fitAddon) {
|
|
9375
9776
|
state.fitAddon.fit();
|
|
9376
9777
|
maybeScrollTerminalToBottom("resize");
|
|
9377
|
-
if (state.
|
|
9378
|
-
|
|
9379
|
-
|
|
9380
|
-
|
|
9381
|
-
|
|
9382
|
-
|
|
9383
|
-
|
|
9384
|
-
|
|
9385
|
-
|
|
9386
|
-
|
|
9387
|
-
body: JSON.stringify(nextSize)
|
|
9388
|
-
}).catch(function() {});
|
|
9389
|
-
}
|
|
9778
|
+
if (state.terminal) {
|
|
9779
|
+
sendTerminalResize(state.terminal.cols, state.terminal.rows);
|
|
9780
|
+
}
|
|
9781
|
+
var output = document.getElementById("output");
|
|
9782
|
+
if (output && state.terminal) {
|
|
9783
|
+
var containerW = output.getBoundingClientRect().width;
|
|
9784
|
+
var expectedMinCols = Math.floor(containerW / 20);
|
|
9785
|
+
if (state.terminal.cols < expectedMinCols && attempt < maxAttempts) {
|
|
9786
|
+
requestAnimationFrame(tryFit);
|
|
9787
|
+
return;
|
|
9390
9788
|
}
|
|
9391
9789
|
}
|
|
9790
|
+
finishFit();
|
|
9392
9791
|
} else if (attempt < maxAttempts) {
|
|
9393
9792
|
requestAnimationFrame(tryFit);
|
|
9793
|
+
} else {
|
|
9794
|
+
finishFit();
|
|
9394
9795
|
}
|
|
9395
9796
|
}
|
|
9396
9797
|
requestAnimationFrame(tryFit);
|
|
@@ -9419,27 +9820,7 @@
|
|
|
9419
9820
|
maybeScrollTerminalToBottom("resize");
|
|
9420
9821
|
}
|
|
9421
9822
|
|
|
9422
|
-
|
|
9423
|
-
cols: state.terminal.cols,
|
|
9424
|
-
rows: state.terminal.rows
|
|
9425
|
-
};
|
|
9426
|
-
|
|
9427
|
-
if (!state.selectedId) return;
|
|
9428
|
-
|
|
9429
|
-
// Skip resize for structured sessions (no PTY)
|
|
9430
|
-
var resizeSess = state.sessions.find(function(s) { return s.id === state.selectedId; });
|
|
9431
|
-
if (isStructuredSession(resizeSess)) return;
|
|
9432
|
-
|
|
9433
|
-
// Only send resize API call if dimensions actually changed
|
|
9434
|
-
if (state.lastResize.cols !== nextSize.cols || state.lastResize.rows !== nextSize.rows) {
|
|
9435
|
-
state.lastResize = nextSize;
|
|
9436
|
-
fetch("/api/sessions/" + state.selectedId + "/resize", {
|
|
9437
|
-
method: "POST",
|
|
9438
|
-
headers: { "Content-Type": "application/json" },
|
|
9439
|
-
credentials: "same-origin",
|
|
9440
|
-
body: JSON.stringify(nextSize)
|
|
9441
|
-
}).catch(function() {});
|
|
9442
|
-
}
|
|
9823
|
+
sendTerminalResize(state.terminal.cols, state.terminal.rows);
|
|
9443
9824
|
}
|
|
9444
9825
|
|
|
9445
9826
|
function startPolling() {
|
|
@@ -9482,6 +9863,12 @@
|
|
|
9482
9863
|
subscribeToSession(state.selectedId);
|
|
9483
9864
|
// Flush pending messages after reconnection
|
|
9484
9865
|
flushPendingMessages();
|
|
9866
|
+
// Re-fit terminal on reconnect — the viewport may have changed
|
|
9867
|
+
// while disconnected, and the PTY needs up-to-date dimensions.
|
|
9868
|
+
if (state.terminal && state.fitAddon) {
|
|
9869
|
+
state.terminalViewportSize = { width: 0, height: 0 };
|
|
9870
|
+
ensureTerminalFit();
|
|
9871
|
+
}
|
|
9485
9872
|
};
|
|
9486
9873
|
|
|
9487
9874
|
ws.onmessage = function(event) {
|
|
@@ -9615,16 +10002,7 @@
|
|
|
9615
10002
|
} else {
|
|
9616
10003
|
endedNotifBody = endedSession ? (endedSession.command || msg.sessionId) : msg.sessionId;
|
|
9617
10004
|
}
|
|
9618
|
-
|
|
9619
|
-
endedNotifTitle,
|
|
9620
|
-
endedNotifBody,
|
|
9621
|
-
{
|
|
9622
|
-
tag: "wand-ended-" + msg.sessionId,
|
|
9623
|
-
onClick: function() {
|
|
9624
|
-
if (msg.sessionId !== state.selectedId) selectSession(msg.sessionId);
|
|
9625
|
-
}
|
|
9626
|
-
}
|
|
9627
|
-
);
|
|
10005
|
+
notifyTaskEnded(msg.sessionId, endedNotifTitle, endedNotifBody);
|
|
9628
10006
|
if (msg.sessionId !== state.selectedId || document.hidden) {
|
|
9629
10007
|
showNotificationBubble({
|
|
9630
10008
|
title: endedNotifTitle,
|
|
@@ -9710,6 +10088,7 @@
|
|
|
9710
10088
|
state.currentTask = msg.data || null;
|
|
9711
10089
|
updateTaskDisplay();
|
|
9712
10090
|
}
|
|
10091
|
+
notifyTaskProgress(msg.sessionId, msg.data || null);
|
|
9713
10092
|
// Update session list to reflect current activity (debounced)
|
|
9714
10093
|
scheduleSessionListUpdate();
|
|
9715
10094
|
break;
|
|
@@ -9756,18 +10135,7 @@
|
|
|
9756
10135
|
} else {
|
|
9757
10136
|
permBody += "\n" + permDetail;
|
|
9758
10137
|
}
|
|
9759
|
-
|
|
9760
|
-
"需要你的授权",
|
|
9761
|
-
permBody,
|
|
9762
|
-
{
|
|
9763
|
-
tag: "wand-perm-" + msg.sessionId,
|
|
9764
|
-
onClick: function() {
|
|
9765
|
-
if (msg.sessionId !== state.selectedId) {
|
|
9766
|
-
selectSession(msg.sessionId);
|
|
9767
|
-
}
|
|
9768
|
-
}
|
|
9769
|
-
}
|
|
9770
|
-
);
|
|
10138
|
+
notifyPermissionRequest(msg.sessionId, permBody);
|
|
9771
10139
|
// In-app bubble if not currently viewing this session
|
|
9772
10140
|
if (msg.sessionId !== state.selectedId) {
|
|
9773
10141
|
showNotificationBubble({
|
|
@@ -9811,12 +10179,7 @@
|
|
|
9811
10179
|
case 'notification':
|
|
9812
10180
|
if (msg.data) {
|
|
9813
10181
|
if (msg.data.kind === "update") {
|
|
9814
|
-
|
|
9815
|
-
sendBrowserNotification(
|
|
9816
|
-
"Wand \u53d1\u73b0\u65b0\u7248\u672c",
|
|
9817
|
-
"\u5f53\u524d " + (msg.data.current || "-") + " \u2192 \u6700\u65b0 " + (msg.data.latest || "-"),
|
|
9818
|
-
{ tag: "wand-update" }
|
|
9819
|
-
);
|
|
10182
|
+
notifyUpdateAvailable(msg.data.current || "-", msg.data.latest || "-");
|
|
9820
10183
|
} else if (msg.data.kind === "restart") {
|
|
9821
10184
|
showRestartOverlay();
|
|
9822
10185
|
}
|
|
@@ -10335,7 +10698,7 @@
|
|
|
10335
10698
|
// Only expand the single newest tool card (first chat-message = newest due to column-reverse)
|
|
10336
10699
|
var firstMsg = chatMessages.querySelector(".chat-message:not(.system-info)");
|
|
10337
10700
|
if (firstMsg) {
|
|
10338
|
-
var cards = firstMsg.querySelectorAll(".tool-use-card");
|
|
10701
|
+
var cards = firstMsg.querySelectorAll(".tool-use-card, .inline-diff[data-expand-key]");
|
|
10339
10702
|
if (cards.length > 0) {
|
|
10340
10703
|
var firstCard = cards[0];
|
|
10341
10704
|
var firstCardKey = getElementExpandKey(firstCard);
|
|
@@ -10345,6 +10708,8 @@
|
|
|
10345
10708
|
for (var ci = 1; ci < cards.length; ci++) {
|
|
10346
10709
|
var cardKey = getElementExpandKey(cards[ci]);
|
|
10347
10710
|
if (getPersistedExpandState(cardKey) === null) {
|
|
10711
|
+
// Never collapse unanswered AskUserQuestion cards
|
|
10712
|
+
if (cards[ci].classList.contains("ask-user") && !cards[ci].classList.contains("ask-user-answered")) continue;
|
|
10348
10713
|
cards[ci].classList.add("collapsed");
|
|
10349
10714
|
}
|
|
10350
10715
|
}
|
|
@@ -10360,10 +10725,13 @@
|
|
|
10360
10725
|
// Collapse all tool-use cards except those in the new message elements (marked with animate-in)
|
|
10361
10726
|
// newEls: NodeList/Array of newly added message elements, or null to keep only the first card expanded
|
|
10362
10727
|
function collapseOldToolCards(container, newEls) {
|
|
10363
|
-
var allCards = container.querySelectorAll(".tool-use-card");
|
|
10728
|
+
var allCards = container.querySelectorAll(".tool-use-card, .inline-diff[data-expand-key]");
|
|
10364
10729
|
allCards.forEach(function(c) {
|
|
10365
10730
|
var cardKey = getElementExpandKey(c);
|
|
10366
10731
|
if (getPersistedExpandState(cardKey) !== null) return;
|
|
10732
|
+
// Never collapse unanswered AskUserQuestion cards — the user
|
|
10733
|
+
// needs to interact with the options.
|
|
10734
|
+
if (c.classList.contains("ask-user") && !c.classList.contains("ask-user-answered")) return;
|
|
10367
10735
|
// Keep expanded if this card is inside a newly added message
|
|
10368
10736
|
if (newEls) {
|
|
10369
10737
|
for (var i = 0; i < newEls.length; i++) {
|
|
@@ -10483,11 +10851,13 @@
|
|
|
10483
10851
|
smartScrollToBottom(chatMessages);
|
|
10484
10852
|
});
|
|
10485
10853
|
var newestMsgEl = chatMessages.querySelector(".chat-message");
|
|
10486
|
-
var allCards = chatMessages.querySelectorAll(".tool-use-card");
|
|
10854
|
+
var allCards = chatMessages.querySelectorAll(".tool-use-card, .inline-diff[data-expand-key]");
|
|
10487
10855
|
var newestCard = null;
|
|
10488
10856
|
allCards.forEach(function(c) {
|
|
10489
10857
|
var cardKey = getElementExpandKey(c);
|
|
10490
10858
|
if (getPersistedExpandState(cardKey) !== null) return;
|
|
10859
|
+
// Never collapse unanswered AskUserQuestion cards
|
|
10860
|
+
if (c.classList.contains("ask-user") && !c.classList.contains("ask-user-answered")) return;
|
|
10491
10861
|
if (newestMsgEl && newestMsgEl.contains(c)) {
|
|
10492
10862
|
if (!newestCard) newestCard = c;
|
|
10493
10863
|
else c.classList.add("collapsed");
|
|
@@ -11578,7 +11948,7 @@
|
|
|
11578
11948
|
if (msg.role === "thinking") {
|
|
11579
11949
|
var thinkingKey = buildExpandKey("thinking", [getMessageKey(msg, messageIndex), "pty"]);
|
|
11580
11950
|
var thinkingPersisted = getPersistedExpandState(thinkingKey);
|
|
11581
|
-
var thinkingExpanded = thinkingPersisted === null ?
|
|
11951
|
+
var thinkingExpanded = thinkingPersisted === null ? getCardDefault("thinking") : thinkingPersisted;
|
|
11582
11952
|
return '<div class="chat-message thinking">' +
|
|
11583
11953
|
'<div class="thinking-inline thinking-pty ' + (thinkingExpanded ? 'expanded' : 'collapsed') + '" data-expand-kind="thinking" data-expand-key="' + escapeHtml(thinkingKey) + '" data-thinking="" onclick="__thinkingToggle(this)">' +
|
|
11584
11954
|
'<span class="thinking-inline-icon">⦿</span>' +
|
|
@@ -11716,7 +12086,7 @@
|
|
|
11716
12086
|
var summaryText = parts.join(" · ");
|
|
11717
12087
|
var groupKey = buildExpandKey("tool-group", [messageKey, items[0] && items[0].index, items.length]);
|
|
11718
12088
|
var persistedExpanded = getPersistedExpandState(groupKey);
|
|
11719
|
-
var shouldExpand = persistedExpanded === null ?
|
|
12089
|
+
var shouldExpand = persistedExpanded === null ? getCardDefault("toolGroup") : persistedExpanded;
|
|
11720
12090
|
|
|
11721
12091
|
// Render each item's inline-tool card
|
|
11722
12092
|
var innerHtml = "";
|
|
@@ -11828,7 +12198,7 @@
|
|
|
11828
12198
|
}
|
|
11829
12199
|
var thinkingKey = buildExpandKey("thinking", [messageKey, index]);
|
|
11830
12200
|
var thinkingPersisted = getPersistedExpandState(thinkingKey);
|
|
11831
|
-
var thinkingExpanded = thinkingPersisted === null ?
|
|
12201
|
+
var thinkingExpanded = thinkingPersisted === null ? getCardDefault("thinking") : thinkingPersisted;
|
|
11832
12202
|
return '<div class="thinking-inline ' + (thinkingExpanded ? 'expanded' : 'collapsed') + '" data-expand-kind="thinking" data-expand-key="' + escapeHtml(thinkingKey) + '" data-thinking="' + escapeHtml(thinkingText) + '" onclick="__thinkingToggle(this)">' +
|
|
11833
12203
|
'<span class="thinking-inline-icon">⦿</span>' +
|
|
11834
12204
|
'<span class="thinking-inline-preview">' + escapeHtml(thinkingExpanded ? thinkingText : preview) + '</span>' +
|
|
@@ -11925,7 +12295,7 @@
|
|
|
11925
12295
|
var fullResult = resultContent;
|
|
11926
12296
|
|
|
11927
12297
|
var expandedHtml = "";
|
|
11928
|
-
var shouldExpand = persistedExpanded === null ?
|
|
12298
|
+
var shouldExpand = persistedExpanded === null ? getCardDefault("inlineTools") : persistedExpanded;
|
|
11929
12299
|
if (hasResult) {
|
|
11930
12300
|
expandedHtml = '<div class="inline-tool-expanded" style="display: ' + (shouldExpand ? 'block' : 'none') + ';">' +
|
|
11931
12301
|
'<div class="inline-tool-result">' + formatInlineResult(resultContent, toolName) + '</div>' +
|
|
@@ -12010,7 +12380,7 @@
|
|
|
12010
12380
|
|
|
12011
12381
|
// Show command preview in header (truncate long commands)
|
|
12012
12382
|
var cmdPreview = command.length > 80 ? command.slice(0, 77) + "…" : command;
|
|
12013
|
-
var shouldExpand = persistedExpanded === null ?
|
|
12383
|
+
var shouldExpand = persistedExpanded === null ? getCardDefault("terminal") : persistedExpanded;
|
|
12014
12384
|
|
|
12015
12385
|
var termTruncated = toolResult && toolResult._truncated === true;
|
|
12016
12386
|
var termTruncAttrs = termTruncated
|
|
@@ -12047,10 +12417,11 @@
|
|
|
12047
12417
|
return "";
|
|
12048
12418
|
}
|
|
12049
12419
|
|
|
12050
|
-
function renderDiffTool(block, toolResult, toolName) {
|
|
12420
|
+
function renderDiffTool(block, toolResult, toolName, messageKey, index) {
|
|
12051
12421
|
var inputData = block.input || {};
|
|
12052
12422
|
var path = inputData.file_path || inputData.path || "";
|
|
12053
12423
|
var fileName = path.split("/").pop() || path;
|
|
12424
|
+
var toolId = block.id || "tool-" + toolName + "-" + (typeof index === "number" ? index : 0);
|
|
12054
12425
|
|
|
12055
12426
|
var oldStr = inputData.old_string || "";
|
|
12056
12427
|
var newStr = inputData.new_string || inputData.content || "";
|
|
@@ -12095,16 +12466,26 @@
|
|
|
12095
12466
|
statusText = "执行中";
|
|
12096
12467
|
}
|
|
12097
12468
|
|
|
12469
|
+
// Expand state: respect cardDefaults.editCards and persisted state
|
|
12470
|
+
var expandKey = buildExpandKey("diff", [messageKey, toolId || index, index]);
|
|
12471
|
+
var persistedExpanded = getPersistedExpandState(expandKey);
|
|
12472
|
+
var cardDefaultExpand = getCardDefault("editCards");
|
|
12473
|
+
var shouldExpand = persistedExpanded === null ? (statusClass === "diff-pending" || cardDefaultExpand) : persistedExpanded;
|
|
12474
|
+
var collapsedClass = shouldExpand ? "" : " collapsed";
|
|
12475
|
+
|
|
12098
12476
|
// If only one column has content, show full width
|
|
12099
12477
|
var bothCols = leftCol && rightCol;
|
|
12100
12478
|
var colClass = bothCols ? "diff-col-half" : "diff-col-full";
|
|
12101
12479
|
|
|
12102
|
-
return '<div class="inline-diff" data-tool-name="' + escapeHtml(toolName) + '"
|
|
12103
|
-
'
|
|
12480
|
+
return '<div class="inline-diff' + collapsedClass + '" data-tool-name="' + escapeHtml(toolName) + '"' +
|
|
12481
|
+
' data-expand-kind="diff" data-expand-key="' + escapeHtml(expandKey) + '"' +
|
|
12482
|
+
' data-tool-use-id="' + escapeHtml(toolId) + '">' +
|
|
12483
|
+
'<div class="diff-header" onclick="__tcToggle(event,this)">' +
|
|
12104
12484
|
'<span class="diff-file-icon"></span>' +
|
|
12105
12485
|
'<span class="diff-file-name">' + escapeHtml(fileName) + '</span>' +
|
|
12106
12486
|
'<span class="diff-path">' + escapeHtml(path) + '</span>' +
|
|
12107
12487
|
'<span class="diff-status ' + statusClass + '">' + statusText + '</span>' +
|
|
12488
|
+
'<span class="diff-toggle">▼</span>' +
|
|
12108
12489
|
'</div>' +
|
|
12109
12490
|
'<div class="diff-body">' +
|
|
12110
12491
|
'<div class="diff-columns">' +
|
|
@@ -12138,7 +12519,7 @@
|
|
|
12138
12519
|
|
|
12139
12520
|
// ── Diff-style: Edit, Write, MultiEdit
|
|
12140
12521
|
if (toolName === "Edit" || toolName === "Write" || toolName === "MultiEdit") {
|
|
12141
|
-
return renderDiffTool(block, toolResult, toolName);
|
|
12522
|
+
return renderDiffTool(block, toolResult, toolName, messageKey, index);
|
|
12142
12523
|
}
|
|
12143
12524
|
|
|
12144
12525
|
// ── AskUserQuestion tool — special card with batch submit
|
|
@@ -12297,7 +12678,7 @@
|
|
|
12297
12678
|
|
|
12298
12679
|
var expandKey = buildExpandKey("tool-card", [messageKey, toolId]);
|
|
12299
12680
|
var persistedExpanded = getPersistedExpandState(expandKey);
|
|
12300
|
-
var cardDefaultExpand =
|
|
12681
|
+
var cardDefaultExpand = getCardDefault("editCards");
|
|
12301
12682
|
var shouldExpand = persistedExpanded === null ? (statusClass === "loading" || cardDefaultExpand) : persistedExpanded;
|
|
12302
12683
|
var tcTruncated = toolResult && toolResult._truncated === true;
|
|
12303
12684
|
var collapsedClass = shouldExpand ? "" : " collapsed";
|
|
@@ -12854,13 +13235,39 @@
|
|
|
12854
13235
|
}
|
|
12855
13236
|
}
|
|
12856
13237
|
|
|
13238
|
+
function _shouldSendSystemNotification(opts) {
|
|
13239
|
+
var options = opts || {};
|
|
13240
|
+
if (options.onlyWhenHidden && !document.hidden) return false;
|
|
13241
|
+
if (options.skipWhenSelectedSessionId && options.skipWhenSelectedSessionId === state.selectedId && !document.hidden) {
|
|
13242
|
+
return false;
|
|
13243
|
+
}
|
|
13244
|
+
return true;
|
|
13245
|
+
}
|
|
13246
|
+
|
|
13247
|
+
function _isNotificationThrottled(tag, minIntervalMs) {
|
|
13248
|
+
if (!tag || !minIntervalMs || minIntervalMs <= 0) return false;
|
|
13249
|
+
var lastAt = state.notificationHistory[tag] || 0;
|
|
13250
|
+
var now = Date.now();
|
|
13251
|
+
if (now - lastAt < minIntervalMs) return true;
|
|
13252
|
+
state.notificationHistory[tag] = now;
|
|
13253
|
+
return false;
|
|
13254
|
+
}
|
|
13255
|
+
|
|
12857
13256
|
function sendBrowserNotification(title, body, opts) {
|
|
13257
|
+
var options = opts || {};
|
|
13258
|
+
var tag = options.tag || "";
|
|
13259
|
+
if (!_shouldSendSystemNotification(options)) return;
|
|
13260
|
+
if (_isNotificationThrottled(tag, options.minIntervalMs || 0)) return;
|
|
12858
13261
|
// Native Android bridge path
|
|
12859
13262
|
if (_hasNativeBridge) {
|
|
12860
13263
|
var perm = _getNativePermission();
|
|
12861
13264
|
if (perm !== "granted") return;
|
|
12862
13265
|
try {
|
|
12863
|
-
|
|
13266
|
+
var nativeTag = tag;
|
|
13267
|
+
if (options.kind) {
|
|
13268
|
+
nativeTag = options.kind + (tag ? ":" + tag : "");
|
|
13269
|
+
}
|
|
13270
|
+
WandNative.sendNotification(title || "Wand", body || "", nativeTag || "");
|
|
12864
13271
|
} catch (_e) {}
|
|
12865
13272
|
return;
|
|
12866
13273
|
}
|
|
@@ -12870,13 +13277,13 @@
|
|
|
12870
13277
|
try {
|
|
12871
13278
|
var n = new Notification(title, {
|
|
12872
13279
|
body: body || "",
|
|
12873
|
-
icon:
|
|
12874
|
-
tag:
|
|
13280
|
+
icon: options.icon || "/favicon.ico",
|
|
13281
|
+
tag: tag || undefined,
|
|
12875
13282
|
});
|
|
12876
13283
|
n.onclick = function() {
|
|
12877
13284
|
window.focus();
|
|
12878
13285
|
n.close();
|
|
12879
|
-
if (
|
|
13286
|
+
if (options.onClick) options.onClick();
|
|
12880
13287
|
};
|
|
12881
13288
|
// Auto-close after 10s
|
|
12882
13289
|
setTimeout(function() { n.close(); }, 10000);
|
|
@@ -12885,6 +13292,76 @@
|
|
|
12885
13292
|
}
|
|
12886
13293
|
}
|
|
12887
13294
|
|
|
13295
|
+
function notifyTaskProgress(sessionId, task) {
|
|
13296
|
+
if (!task || !task.title) return;
|
|
13297
|
+
var session = state.sessions.find(function(s) { return s.id === sessionId; });
|
|
13298
|
+
if (!session) return;
|
|
13299
|
+
var sessionLabel = session.summary || session.command || sessionId;
|
|
13300
|
+
sendBrowserNotification(
|
|
13301
|
+
"任务进行中",
|
|
13302
|
+
sessionLabel + "\n" + task.title,
|
|
13303
|
+
{
|
|
13304
|
+
kind: "task",
|
|
13305
|
+
tag: "wand-task-" + sessionId + "-" + task.title,
|
|
13306
|
+
minIntervalMs: 90000,
|
|
13307
|
+
onlyWhenHidden: true,
|
|
13308
|
+
skipWhenSelectedSessionId: sessionId,
|
|
13309
|
+
onClick: function() {
|
|
13310
|
+
if (sessionId !== state.selectedId) selectSession(sessionId);
|
|
13311
|
+
}
|
|
13312
|
+
}
|
|
13313
|
+
);
|
|
13314
|
+
}
|
|
13315
|
+
|
|
13316
|
+
function notifyUpdateAvailable(currentVersion, latestVersion) {
|
|
13317
|
+
showUpdateBubble(currentVersion || "-", latestVersion || "-");
|
|
13318
|
+
sendBrowserNotification(
|
|
13319
|
+
"Wand 发现新版本",
|
|
13320
|
+
"当前 " + (currentVersion || "-") + " → 最新 " + (latestVersion || "-"),
|
|
13321
|
+
{
|
|
13322
|
+
kind: "update",
|
|
13323
|
+
tag: "wand-update",
|
|
13324
|
+
minIntervalMs: 300000,
|
|
13325
|
+
}
|
|
13326
|
+
);
|
|
13327
|
+
}
|
|
13328
|
+
|
|
13329
|
+
function notifyPermissionRequest(sessionId, body) {
|
|
13330
|
+
sendBrowserNotification(
|
|
13331
|
+
"需要你的授权",
|
|
13332
|
+
body,
|
|
13333
|
+
{
|
|
13334
|
+
kind: "permission",
|
|
13335
|
+
tag: "wand-perm-" + sessionId,
|
|
13336
|
+
minIntervalMs: 60000,
|
|
13337
|
+
onlyWhenHidden: true,
|
|
13338
|
+
skipWhenSelectedSessionId: sessionId,
|
|
13339
|
+
onClick: function() {
|
|
13340
|
+
if (sessionId !== state.selectedId) {
|
|
13341
|
+
selectSession(sessionId);
|
|
13342
|
+
}
|
|
13343
|
+
}
|
|
13344
|
+
}
|
|
13345
|
+
);
|
|
13346
|
+
}
|
|
13347
|
+
|
|
13348
|
+
function notifyTaskEnded(sessionId, title, body) {
|
|
13349
|
+
sendBrowserNotification(
|
|
13350
|
+
title,
|
|
13351
|
+
body,
|
|
13352
|
+
{
|
|
13353
|
+
kind: "task-ended",
|
|
13354
|
+
tag: "wand-ended-" + sessionId,
|
|
13355
|
+
minIntervalMs: 10000,
|
|
13356
|
+
onClick: function() {
|
|
13357
|
+
if (sessionId !== state.selectedId) selectSession(sessionId);
|
|
13358
|
+
}
|
|
13359
|
+
}
|
|
13360
|
+
);
|
|
13361
|
+
}
|
|
13362
|
+
|
|
13363
|
+
/**
|
|
13364
|
+
|
|
12888
13365
|
/**
|
|
12889
13366
|
* Play a soft, rounded notification chime using Web Audio API.
|
|
12890
13367
|
* Two ascending sine tones with smooth gain envelope — gentle on the ears.
|
|
@@ -12912,13 +13389,15 @@
|
|
|
12912
13389
|
// Some browsers suspend AudioContext until user gesture — resume it
|
|
12913
13390
|
if (ctx.state === "suspended") ctx.resume();
|
|
12914
13391
|
|
|
13392
|
+
var vol = (state.notifVolume || 0) / 100;
|
|
13393
|
+
|
|
12915
13394
|
function tone(freq, start, dur) {
|
|
12916
13395
|
var osc = ctx.createOscillator();
|
|
12917
13396
|
var gain = ctx.createGain();
|
|
12918
13397
|
osc.type = "sine";
|
|
12919
13398
|
osc.frequency.value = freq;
|
|
12920
13399
|
gain.gain.setValueAtTime(0, ctx.currentTime + start);
|
|
12921
|
-
gain.gain.linearRampToValueAtTime(0.
|
|
13400
|
+
gain.gain.linearRampToValueAtTime(0.5 * vol, ctx.currentTime + start + 0.04);
|
|
12922
13401
|
gain.gain.exponentialRampToValueAtTime(0.001, ctx.currentTime + start + dur);
|
|
12923
13402
|
osc.connect(gain);
|
|
12924
13403
|
gain.connect(ctx.destination);
|