@co0ontty/wand 1.14.6 → 1.15.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/message-truncator.js +0 -2
- package/dist/process-manager.js +38 -65
- package/dist/pwa.js +9 -2
- package/dist/server.js +15 -4
- package/dist/structured-session-manager.js +21 -4
- package/dist/types.d.ts +0 -2
- package/dist/web-ui/content/scripts.js +464 -145
- package/dist/web-ui/content/styles.css +596 -53
- package/package.json +3 -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,
|
|
@@ -134,9 +135,14 @@
|
|
|
134
135
|
ws: null,
|
|
135
136
|
wsConnected: false,
|
|
136
137
|
_updateBubbleShown: false,
|
|
138
|
+
notificationHistory: {},
|
|
139
|
+
delayedNotificationTimer: null,
|
|
137
140
|
notifSound: (function() {
|
|
138
141
|
try { var v = localStorage.getItem("wand-notif-sound"); return v === null ? true : v === "true"; } catch (e) { return true; }
|
|
139
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
|
+
})(),
|
|
140
146
|
notifBubble: (function() {
|
|
141
147
|
try { var v = localStorage.getItem("wand-notif-bubble"); return v === null ? true : v === "true"; } catch (e) { return true; }
|
|
142
148
|
})(),
|
|
@@ -891,8 +897,7 @@
|
|
|
891
897
|
refreshAll();
|
|
892
898
|
requestNotificationPermission();
|
|
893
899
|
if (config.updateAvailable && config.latestVersion) {
|
|
894
|
-
|
|
895
|
-
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);
|
|
896
901
|
}
|
|
897
902
|
if (state.claudeHistoryExpanded && !state.claudeHistoryLoaded) {
|
|
898
903
|
loadClaudeHistory();
|
|
@@ -1316,23 +1321,55 @@
|
|
|
1316
1321
|
function renderSettingsModal() {
|
|
1317
1322
|
return '<section id="settings-modal" class="modal-backdrop hidden">' +
|
|
1318
1323
|
'<div class="modal settings-modal">' +
|
|
1319
|
-
'<div class="modal-header">' +
|
|
1320
|
-
'<
|
|
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>' +
|
|
1321
1329
|
'<button id="close-settings-button" class="btn btn-ghost btn-icon">×</button>' +
|
|
1322
1330
|
'</div>' +
|
|
1323
|
-
'<div class="modal-body">' +
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
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">' +
|
|
1333
1366
|
|
|
1334
1367
|
// About tab
|
|
1335
|
-
'<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>' +
|
|
1336
1373
|
'<div class="settings-about-info">' +
|
|
1337
1374
|
'<div class="settings-about-row"><span class="settings-label">包名</span><span class="settings-value" id="settings-pkg-name">-</span></div>' +
|
|
1338
1375
|
'<div class="settings-about-row"><span class="settings-label">当前版本</span><span class="settings-value" id="settings-version">-</span></div>' +
|
|
@@ -1379,13 +1416,24 @@
|
|
|
1379
1416
|
'</div>' +
|
|
1380
1417
|
|
|
1381
1418
|
// Notifications tab
|
|
1382
|
-
'<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>' +
|
|
1383
1424
|
'<div class="settings-section-title">\u901a\u77e5\u504f\u597d</div>' +
|
|
1384
1425
|
'<div class="field field-inline">' +
|
|
1385
1426
|
'<input id="cfg-notif-sound" type="checkbox" class="field-checkbox" />' +
|
|
1386
1427
|
'<label class="field-label" for="cfg-notif-sound">\u64ad\u653e\u63d0\u793a\u97f3</label>' +
|
|
1387
1428
|
'</div>' +
|
|
1388
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>' +
|
|
1389
1437
|
'<div class="field field-inline">' +
|
|
1390
1438
|
'<input id="cfg-notif-bubble" type="checkbox" class="field-checkbox" />' +
|
|
1391
1439
|
'<label class="field-label" for="cfg-notif-bubble">\u5e94\u7528\u5185\u901a\u77e5\u6c14\u6ce1</label>' +
|
|
@@ -1412,13 +1460,18 @@
|
|
|
1412
1460
|
'<button id="notification-request-btn" class="btn btn-ghost btn-sm hidden">\u6388\u6743\u901a\u77e5</button>' +
|
|
1413
1461
|
'<button id="notification-reset-btn" class="btn btn-ghost btn-sm hidden">\u91cd\u65b0\u6388\u6743</button>' +
|
|
1414
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>' +
|
|
1415
1464
|
'</div>' +
|
|
1416
1465
|
'<p id="notification-test-message" class="hint hidden"></p>' +
|
|
1417
1466
|
'</div>' +
|
|
1418
1467
|
'</div>' +
|
|
1419
1468
|
|
|
1420
1469
|
// General config tab
|
|
1421
|
-
'<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>' +
|
|
1422
1475
|
'<div class="field-row">' +
|
|
1423
1476
|
'<div class="field">' +
|
|
1424
1477
|
'<label class="field-label" for="cfg-host">监听地址 (host)</label>' +
|
|
@@ -1492,12 +1545,18 @@
|
|
|
1492
1545
|
'<p id="app-icon-message" class="hint hidden" style="margin-top:8px"></p>' +
|
|
1493
1546
|
'</div>'
|
|
1494
1547
|
: '') +
|
|
1495
|
-
'<
|
|
1496
|
-
|
|
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>' +
|
|
1497
1552
|
'</div>' +
|
|
1498
1553
|
|
|
1499
1554
|
// Security tab
|
|
1500
|
-
'<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>' +
|
|
1501
1560
|
'<div class="settings-card">' +
|
|
1502
1561
|
'<h3 class="settings-section-title">\ud83d\udd12 修改密码</h3>' +
|
|
1503
1562
|
'<div class="field">' +
|
|
@@ -1529,14 +1588,22 @@
|
|
|
1529
1588
|
'</div>' +
|
|
1530
1589
|
|
|
1531
1590
|
// Command presets tab
|
|
1532
|
-
'<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>' +
|
|
1533
1596
|
'<div id="presets-list" class="presets-list"></div>' +
|
|
1534
1597
|
'</div>' +
|
|
1535
1598
|
|
|
1536
1599
|
// Display settings tab
|
|
1537
|
-
'<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>' +
|
|
1538
1605
|
'<div class="settings-section-title">卡片默认展开状态</div>' +
|
|
1539
|
-
'<p class="hint
|
|
1606
|
+
'<p class="hint settings-inline-hint">设置结构化聊天视图中各类卡片的默认展开/折叠状态。手动操作的展开状态优先于此默认设置。</p>' +
|
|
1540
1607
|
'<div class="switch-card-list">' +
|
|
1541
1608
|
'<label class="switch-card" for="cfg-card-edit">' +
|
|
1542
1609
|
'<div class="switch-card-header">' +
|
|
@@ -1579,8 +1646,10 @@
|
|
|
1579
1646
|
'<div class="switch-card-desc">连续同类工具调用的折叠组</div>' +
|
|
1580
1647
|
'</label>' +
|
|
1581
1648
|
'</div>' +
|
|
1582
|
-
'<
|
|
1583
|
-
|
|
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>' +
|
|
1584
1653
|
'</div>' +
|
|
1585
1654
|
'</div>' +
|
|
1586
1655
|
'</div>' +
|
|
@@ -2796,6 +2865,29 @@
|
|
|
2796
2865
|
});
|
|
2797
2866
|
}
|
|
2798
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
|
+
|
|
2799
2891
|
window.__tcToggle = function(e, headerEl) {
|
|
2800
2892
|
var card = headerEl.closest(".tool-use-card") || headerEl.closest(".inline-diff");
|
|
2801
2893
|
if (card) {
|
|
@@ -2803,23 +2895,16 @@
|
|
|
2803
2895
|
card.classList.toggle("collapsed");
|
|
2804
2896
|
var expandKind = card.dataset.expandKind || "tool-card";
|
|
2805
2897
|
persistElementExpandState(card, expandKind);
|
|
2806
|
-
|
|
2807
|
-
if (wasCollapsed && card.dataset.truncated === "true" && card.dataset.loaded !== "true") {
|
|
2808
|
-
var toolUseId = card.dataset.toolUseId;
|
|
2898
|
+
if (wasCollapsed) {
|
|
2809
2899
|
var resultDiv = card.querySelector(".tool-use-result");
|
|
2810
|
-
|
|
2811
|
-
|
|
2812
|
-
|
|
2813
|
-
|
|
2814
|
-
if (resultDiv) resultDiv.innerHTML = '<div class="tool-content-error" onclick="__tcToggle(null, card.querySelector(\'.tool-use-header\'))">加载失败,点击重试</div>';
|
|
2815
|
-
card.dataset.loaded = "";
|
|
2816
|
-
} else {
|
|
2817
|
-
card.dataset.truncated = "false";
|
|
2818
|
-
card.dataset.loaded = "true";
|
|
2819
|
-
var content = typeof data.content === "string" ? data.content : JSON.stringify(data.content);
|
|
2900
|
+
lazyLoadTruncatedToolContent(
|
|
2901
|
+
card,
|
|
2902
|
+
resultDiv,
|
|
2903
|
+
function(content) {
|
|
2820
2904
|
if (resultDiv) resultDiv.innerHTML = '<pre class="tool-use-result-content">' + escapeHtml(content) + '</pre>';
|
|
2821
|
-
}
|
|
2822
|
-
|
|
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
|
+
);
|
|
2823
2908
|
}
|
|
2824
2909
|
}
|
|
2825
2910
|
if (e) { e.preventDefault(); e.stopPropagation(); }
|
|
@@ -2859,22 +2944,10 @@
|
|
|
2859
2944
|
statusSpan.textContent = "✓";
|
|
2860
2945
|
}
|
|
2861
2946
|
}
|
|
2862
|
-
|
|
2863
|
-
|
|
2864
|
-
|
|
2865
|
-
|
|
2866
|
-
el.dataset.loaded = "loading";
|
|
2867
|
-
__fetchToolContent(toolUseId, function(err, data) {
|
|
2868
|
-
if (err) {
|
|
2869
|
-
if (body) body.innerHTML = '<div class="tool-content-error">加载失败,点击重试</div>';
|
|
2870
|
-
el.dataset.loaded = "";
|
|
2871
|
-
} else {
|
|
2872
|
-
el.dataset.truncated = "false";
|
|
2873
|
-
el.dataset.loaded = "true";
|
|
2874
|
-
var content = typeof data.content === "string" ? data.content : JSON.stringify(data.content);
|
|
2875
|
-
el.dataset.result = content;
|
|
2876
|
-
if (body) body.innerHTML = '<div class="inline-tool-result">' + formatInlineResult(content, "") + '</div>';
|
|
2877
|
-
}
|
|
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>';
|
|
2878
2951
|
});
|
|
2879
2952
|
}
|
|
2880
2953
|
persistElementExpandState(el, "inline-tool");
|
|
@@ -2891,29 +2964,17 @@
|
|
|
2891
2964
|
var toggleIcon = el.querySelector(".term-toggle-icon");
|
|
2892
2965
|
if (toggleIcon) toggleIcon.textContent = isHidden ? "▼" : "▶";
|
|
2893
2966
|
persistElementExpandState(container, "terminal");
|
|
2894
|
-
|
|
2895
|
-
if (isHidden && container.dataset.truncated === "true" && container.dataset.loaded !== "true") {
|
|
2896
|
-
var toolUseId = container.dataset.toolUseId;
|
|
2967
|
+
if (isHidden) {
|
|
2897
2968
|
var termOutput = body.querySelector(".term-output");
|
|
2898
|
-
|
|
2899
|
-
|
|
2900
|
-
|
|
2901
|
-
|
|
2902
|
-
|
|
2903
|
-
|
|
2904
|
-
|
|
2905
|
-
container.dataset.truncated = "false";
|
|
2906
|
-
container.dataset.loaded = "true";
|
|
2907
|
-
var content = typeof data.content === "string" ? data.content : JSON.stringify(data.content);
|
|
2908
|
-
if (termOutput) {
|
|
2909
|
-
var lines = content.split("\n");
|
|
2910
|
-
var html = "";
|
|
2911
|
-
for (var i = 0; i < lines.length; i++) {
|
|
2912
|
-
if (!lines[i] && i === lines.length - 1) continue;
|
|
2913
|
-
html += '<div class="term-line">' + escapeHtml(lines[i]) + '</div>';
|
|
2914
|
-
}
|
|
2915
|
-
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>';
|
|
2916
2976
|
}
|
|
2977
|
+
termOutput.innerHTML = html;
|
|
2917
2978
|
}
|
|
2918
2979
|
});
|
|
2919
2980
|
}
|
|
@@ -3235,6 +3296,33 @@
|
|
|
3235
3296
|
try { localStorage.setItem("wand-notif-sound", String(state.notifSound)); } catch (e) {}
|
|
3236
3297
|
// Preview sound when toggling on
|
|
3237
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();
|
|
3238
3326
|
});
|
|
3239
3327
|
}
|
|
3240
3328
|
var notifBubbleEl = document.getElementById("cfg-notif-bubble");
|
|
@@ -3262,6 +3350,8 @@
|
|
|
3262
3350
|
if (notifResetBtn) notifResetBtn.addEventListener("click", resetNotificationPermission);
|
|
3263
3351
|
var notifTestBtn = document.getElementById("notification-test-btn");
|
|
3264
3352
|
if (notifTestBtn) notifTestBtn.addEventListener("click", testNotification);
|
|
3353
|
+
var notifTestDelayBtn = document.getElementById("notification-test-delay-btn");
|
|
3354
|
+
if (notifTestDelayBtn) notifTestDelayBtn.addEventListener("click", scheduleTestNotification);
|
|
3265
3355
|
updateNotificationStatus();
|
|
3266
3356
|
// Native notification sound selector (APK only)
|
|
3267
3357
|
if (_hasNativeBridge && typeof WandNative.getAvailableSounds === "function") {
|
|
@@ -5240,7 +5330,6 @@
|
|
|
5240
5330
|
}
|
|
5241
5331
|
state.selectedId = id;
|
|
5242
5332
|
persistSelectedId();
|
|
5243
|
-
// Clear tool content cache on session switch
|
|
5244
5333
|
state.toolContentCache = {};
|
|
5245
5334
|
// Clear queued inputs from the previous session to prevent cross-session leaks
|
|
5246
5335
|
state.messageQueue = [];
|
|
@@ -5603,6 +5692,7 @@
|
|
|
5603
5692
|
if (confirmEl) confirmEl.value = "";
|
|
5604
5693
|
hideSettingsMessages();
|
|
5605
5694
|
setupFocusTrap(modal);
|
|
5695
|
+
bindSettingsTabKeyboardNavigation();
|
|
5606
5696
|
// Activate first tab
|
|
5607
5697
|
switchSettingsTab("about");
|
|
5608
5698
|
// Load settings data
|
|
@@ -5612,18 +5702,35 @@
|
|
|
5612
5702
|
var bubbleEl = document.getElementById("cfg-notif-bubble");
|
|
5613
5703
|
if (soundEl) soundEl.checked = state.notifSound;
|
|
5614
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";
|
|
5615
5713
|
updateNotificationStatus();
|
|
5616
5714
|
// Load current app icon selection (APK only)
|
|
5617
5715
|
if (typeof WandNative !== "undefined" && typeof WandNative.getAppIcon === "function") {
|
|
5618
5716
|
try { _updateAppIconSelection(WandNative.getAppIcon() || "shorthair"); } catch (_e) {}
|
|
5619
5717
|
}
|
|
5620
|
-
// Sync native notification sound selector (APK only)
|
|
5718
|
+
// Sync native notification sound selector and volume (APK only)
|
|
5621
5719
|
if (_hasNativeBridge && typeof WandNative.getNotificationSound === "function") {
|
|
5622
5720
|
try {
|
|
5623
5721
|
var nsSel = document.getElementById("native-sound-select");
|
|
5624
5722
|
if (nsSel) nsSel.value = WandNative.getNotificationSound();
|
|
5625
5723
|
} catch (_e) {}
|
|
5626
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
|
+
}
|
|
5627
5734
|
}
|
|
5628
5735
|
}
|
|
5629
5736
|
|
|
@@ -5700,21 +5807,76 @@
|
|
|
5700
5807
|
var tabs = document.querySelectorAll(".settings-tab");
|
|
5701
5808
|
var panels = document.querySelectorAll(".settings-panel");
|
|
5702
5809
|
for (var i = 0; i < tabs.length; i++) {
|
|
5703
|
-
|
|
5810
|
+
var isActive = tabs[i].getAttribute("data-tab") === tabName;
|
|
5811
|
+
if (isActive) {
|
|
5704
5812
|
tabs[i].classList.add("active");
|
|
5705
5813
|
} else {
|
|
5706
5814
|
tabs[i].classList.remove("active");
|
|
5707
5815
|
}
|
|
5816
|
+
tabs[i].setAttribute("aria-selected", isActive ? "true" : "false");
|
|
5817
|
+
tabs[i].setAttribute("tabindex", isActive ? "0" : "-1");
|
|
5708
5818
|
}
|
|
5709
5819
|
for (var j = 0; j < panels.length; j++) {
|
|
5710
|
-
|
|
5820
|
+
var isPanelActive = panels[j].id === "settings-tab-" + tabName;
|
|
5821
|
+
if (isPanelActive) {
|
|
5711
5822
|
panels[j].classList.add("active");
|
|
5823
|
+
panels[j].removeAttribute("hidden");
|
|
5712
5824
|
} else {
|
|
5713
5825
|
panels[j].classList.remove("active");
|
|
5826
|
+
panels[j].setAttribute("hidden", "hidden");
|
|
5714
5827
|
}
|
|
5715
5828
|
}
|
|
5716
5829
|
}
|
|
5717
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
|
+
|
|
5718
5880
|
function copyToClipboard(text, triggerBtn) {
|
|
5719
5881
|
if (!text) return;
|
|
5720
5882
|
navigator.clipboard.writeText(text).then(function() {
|
|
@@ -5759,6 +5921,7 @@
|
|
|
5759
5921
|
fetch("/api/settings", { credentials: "same-origin" })
|
|
5760
5922
|
.then(function(res) { return res.json(); })
|
|
5761
5923
|
.then(function(data) {
|
|
5924
|
+
updateSettingsSidebarStatus(data);
|
|
5762
5925
|
// About
|
|
5763
5926
|
var nameEl = document.getElementById("settings-pkg-name");
|
|
5764
5927
|
var verEl = document.getElementById("settings-version");
|
|
@@ -5843,7 +6006,7 @@
|
|
|
5843
6006
|
apkMessageEl.classList.remove("hidden");
|
|
5844
6007
|
}
|
|
5845
6008
|
} else {
|
|
5846
|
-
// ──
|
|
6009
|
+
// ── 浏览器模式:显示线上版本 + 本地版本 + 下载按钮 ──
|
|
5847
6010
|
if (androidApk.github && apkGithubRow && apkGithubEl) {
|
|
5848
6011
|
var ghLabel2 = androidApk.github.version ? ("v" + androidApk.github.version) : androidApk.github.fileName;
|
|
5849
6012
|
if (typeof androidApk.github.size === "number") ghLabel2 += " · " + formatBytes(androidApk.github.size);
|
|
@@ -5856,7 +6019,22 @@
|
|
|
5856
6019
|
window.open(androidApk.github.downloadUrl, "_blank");
|
|
5857
6020
|
};
|
|
5858
6021
|
}
|
|
5859
|
-
}
|
|
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) {
|
|
5860
6038
|
apkMessageEl.textContent = "暂未提供";
|
|
5861
6039
|
apkMessageEl.classList.remove("hidden");
|
|
5862
6040
|
}
|
|
@@ -6262,9 +6440,44 @@
|
|
|
6262
6440
|
});
|
|
6263
6441
|
}
|
|
6264
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
|
+
|
|
6265
6473
|
function testNotification() {
|
|
6266
6474
|
var testMsgEl = document.getElementById("notification-test-message");
|
|
6267
6475
|
var results = [];
|
|
6476
|
+
if (state.delayedNotificationTimer) {
|
|
6477
|
+
clearTimeout(state.delayedNotificationTimer);
|
|
6478
|
+
state.delayedNotificationTimer = null;
|
|
6479
|
+
resetDelayedNotificationButton();
|
|
6480
|
+
}
|
|
6268
6481
|
|
|
6269
6482
|
// 1. Test sound playback
|
|
6270
6483
|
var soundOk = tryPlayNotificationSound();
|
|
@@ -9549,8 +9762,13 @@
|
|
|
9549
9762
|
}
|
|
9550
9763
|
|
|
9551
9764
|
function ensureTerminalFit() {
|
|
9765
|
+
if (state.terminalFitInProgress) return;
|
|
9766
|
+
state.terminalFitInProgress = true;
|
|
9552
9767
|
var maxAttempts = 20;
|
|
9553
9768
|
var attempt = 0;
|
|
9769
|
+
function finishFit() {
|
|
9770
|
+
state.terminalFitInProgress = false;
|
|
9771
|
+
}
|
|
9554
9772
|
function tryFit() {
|
|
9555
9773
|
attempt++;
|
|
9556
9774
|
state.terminalViewportSize = { width: 0, height: 0 };
|
|
@@ -9560,20 +9778,20 @@
|
|
|
9560
9778
|
if (state.terminal) {
|
|
9561
9779
|
sendTerminalResize(state.terminal.cols, state.terminal.rows);
|
|
9562
9780
|
}
|
|
9563
|
-
// Validate: if the fitted cols look suspiciously small relative to
|
|
9564
|
-
// the container width, schedule another attempt — the font metrics
|
|
9565
|
-
// or layout may not have settled yet.
|
|
9566
9781
|
var output = document.getElementById("output");
|
|
9567
9782
|
if (output && state.terminal) {
|
|
9568
9783
|
var containerW = output.getBoundingClientRect().width;
|
|
9569
|
-
var expectedMinCols = Math.floor(containerW / 20);
|
|
9784
|
+
var expectedMinCols = Math.floor(containerW / 20);
|
|
9570
9785
|
if (state.terminal.cols < expectedMinCols && attempt < maxAttempts) {
|
|
9571
9786
|
requestAnimationFrame(tryFit);
|
|
9572
9787
|
return;
|
|
9573
9788
|
}
|
|
9574
9789
|
}
|
|
9790
|
+
finishFit();
|
|
9575
9791
|
} else if (attempt < maxAttempts) {
|
|
9576
9792
|
requestAnimationFrame(tryFit);
|
|
9793
|
+
} else {
|
|
9794
|
+
finishFit();
|
|
9577
9795
|
}
|
|
9578
9796
|
}
|
|
9579
9797
|
requestAnimationFrame(tryFit);
|
|
@@ -9688,15 +9906,17 @@
|
|
|
9688
9906
|
case 'output':
|
|
9689
9907
|
// Update session output (for terminal display and local message parsing)
|
|
9690
9908
|
// NOTE: For structured sessions, output may be "" during streaming — check messages too
|
|
9691
|
-
if (msg.data &&
|
|
9692
|
-
var
|
|
9909
|
+
if (msg.data && msg.sessionId) {
|
|
9910
|
+
var isIncremental = !!msg.data.incremental;
|
|
9911
|
+
var snapshot = { id: msg.sessionId };
|
|
9912
|
+
|
|
9913
|
+
// Carry over small metadata fields present in both modes
|
|
9914
|
+
if (!isIncremental && msg.data.output !== undefined) {
|
|
9915
|
+
snapshot.output = msg.data.output;
|
|
9916
|
+
}
|
|
9693
9917
|
if (Object.prototype.hasOwnProperty.call(msg.data, 'permissionBlocked')) {
|
|
9694
9918
|
snapshot.permissionBlocked = !!msg.data.permissionBlocked;
|
|
9695
9919
|
}
|
|
9696
|
-
// Pass structured messages if available from JSON chat mode
|
|
9697
|
-
if (msg.data.messages) {
|
|
9698
|
-
snapshot.messages = msg.data.messages;
|
|
9699
|
-
}
|
|
9700
9920
|
if (Object.prototype.hasOwnProperty.call(msg.data, 'queuedMessages')) {
|
|
9701
9921
|
snapshot.queuedMessages = msg.data.queuedMessages || [];
|
|
9702
9922
|
state.queueEpoch++;
|
|
@@ -9704,19 +9924,44 @@
|
|
|
9704
9924
|
if (msg.data.structuredState) {
|
|
9705
9925
|
snapshot.structuredState = msg.data.structuredState;
|
|
9706
9926
|
}
|
|
9707
|
-
|
|
9708
|
-
|
|
9709
|
-
|
|
9710
|
-
|
|
9711
|
-
|
|
9712
|
-
//
|
|
9713
|
-
|
|
9714
|
-
|
|
9715
|
-
|
|
9716
|
-
|
|
9927
|
+
if (msg.data.sessionKind) {
|
|
9928
|
+
snapshot.sessionKind = msg.data.sessionKind;
|
|
9929
|
+
}
|
|
9930
|
+
|
|
9931
|
+
if (isIncremental && msg.data.lastMessage) {
|
|
9932
|
+
// Incremental mode: merge lastMessage into existing session messages
|
|
9933
|
+
var existingSession = state.sessions.find(function(s) { return s.id === msg.sessionId; });
|
|
9934
|
+
if (existingSession) {
|
|
9935
|
+
var msgs = Array.isArray(existingSession.messages) ? existingSession.messages.slice() : [];
|
|
9936
|
+
var expectedCount = msg.data.messageCount || 0;
|
|
9937
|
+
// Replace last turn if same role, or append if new turn
|
|
9938
|
+
if (msgs.length > 0 && msg.data.lastMessage.role && msgs[msgs.length - 1].role === msg.data.lastMessage.role) {
|
|
9939
|
+
msgs[msgs.length - 1] = msg.data.lastMessage;
|
|
9940
|
+
} else if (msgs.length < expectedCount) {
|
|
9941
|
+
msgs.push(msg.data.lastMessage);
|
|
9942
|
+
}
|
|
9943
|
+
snapshot.messages = msgs;
|
|
9717
9944
|
}
|
|
9945
|
+
} else if (!isIncremental && msg.data.messages) {
|
|
9946
|
+
// Full mode (backward compatible)
|
|
9947
|
+
snapshot.messages = msg.data.messages;
|
|
9718
9948
|
}
|
|
9719
9949
|
|
|
9950
|
+
// Only update if we have meaningful data
|
|
9951
|
+
if (snapshot.output !== undefined || snapshot.messages || isIncremental || msg.data.permissionBlocked !== undefined) {
|
|
9952
|
+
updateSessionSnapshot(snapshot);
|
|
9953
|
+
if (msg.sessionId === state.selectedId) {
|
|
9954
|
+
var updatedSession = state.sessions.find(function(s) { return s.id === msg.sessionId; }) || snapshot;
|
|
9955
|
+
state.currentMessages = buildMessagesForRender(updatedSession, getPreferredMessages(updatedSession, updatedSession.output, false));
|
|
9956
|
+
updateTaskDisplay();
|
|
9957
|
+
// Structured sessions: render immediately for responsiveness
|
|
9958
|
+
if (updatedSession.sessionKind === 'structured' || msg.data.sessionKind === 'structured') {
|
|
9959
|
+
renderChat();
|
|
9960
|
+
} else {
|
|
9961
|
+
scheduleChatRender();
|
|
9962
|
+
}
|
|
9963
|
+
}
|
|
9964
|
+
}
|
|
9720
9965
|
}
|
|
9721
9966
|
// Real-time terminal output
|
|
9722
9967
|
if (msg.sessionId === state.selectedId && state.terminal && msg.data) {
|
|
@@ -9734,8 +9979,8 @@
|
|
|
9734
9979
|
maybeScrollTerminalToBottom("output");
|
|
9735
9980
|
updateTerminalJumpToBottomButton();
|
|
9736
9981
|
scheduleMobileDomUpdate();
|
|
9737
|
-
} else if (Object.prototype.hasOwnProperty.call(msg.data, "output")) {
|
|
9738
|
-
// Fallback: no chunk available, use full-output comparison
|
|
9982
|
+
} else if (!msg.data.incremental && Object.prototype.hasOwnProperty.call(msg.data, "output")) {
|
|
9983
|
+
// Fallback: no chunk available, use full-output comparison (only in full mode)
|
|
9739
9984
|
syncTerminalBuffer(msg.sessionId, msg.data.output || "", { mode: "append" });
|
|
9740
9985
|
}
|
|
9741
9986
|
}
|
|
@@ -9784,16 +10029,7 @@
|
|
|
9784
10029
|
} else {
|
|
9785
10030
|
endedNotifBody = endedSession ? (endedSession.command || msg.sessionId) : msg.sessionId;
|
|
9786
10031
|
}
|
|
9787
|
-
|
|
9788
|
-
endedNotifTitle,
|
|
9789
|
-
endedNotifBody,
|
|
9790
|
-
{
|
|
9791
|
-
tag: "wand-ended-" + msg.sessionId,
|
|
9792
|
-
onClick: function() {
|
|
9793
|
-
if (msg.sessionId !== state.selectedId) selectSession(msg.sessionId);
|
|
9794
|
-
}
|
|
9795
|
-
}
|
|
9796
|
-
);
|
|
10032
|
+
notifyTaskEnded(msg.sessionId, endedNotifTitle, endedNotifBody);
|
|
9797
10033
|
if (msg.sessionId !== state.selectedId || document.hidden) {
|
|
9798
10034
|
showNotificationBubble({
|
|
9799
10035
|
title: endedNotifTitle,
|
|
@@ -9879,6 +10115,7 @@
|
|
|
9879
10115
|
state.currentTask = msg.data || null;
|
|
9880
10116
|
updateTaskDisplay();
|
|
9881
10117
|
}
|
|
10118
|
+
notifyTaskProgress(msg.sessionId, msg.data || null);
|
|
9882
10119
|
// Update session list to reflect current activity (debounced)
|
|
9883
10120
|
scheduleSessionListUpdate();
|
|
9884
10121
|
break;
|
|
@@ -9925,18 +10162,7 @@
|
|
|
9925
10162
|
} else {
|
|
9926
10163
|
permBody += "\n" + permDetail;
|
|
9927
10164
|
}
|
|
9928
|
-
|
|
9929
|
-
"需要你的授权",
|
|
9930
|
-
permBody,
|
|
9931
|
-
{
|
|
9932
|
-
tag: "wand-perm-" + msg.sessionId,
|
|
9933
|
-
onClick: function() {
|
|
9934
|
-
if (msg.sessionId !== state.selectedId) {
|
|
9935
|
-
selectSession(msg.sessionId);
|
|
9936
|
-
}
|
|
9937
|
-
}
|
|
9938
|
-
}
|
|
9939
|
-
);
|
|
10165
|
+
notifyPermissionRequest(msg.sessionId, permBody);
|
|
9940
10166
|
// In-app bubble if not currently viewing this session
|
|
9941
10167
|
if (msg.sessionId !== state.selectedId) {
|
|
9942
10168
|
showNotificationBubble({
|
|
@@ -9980,12 +10206,7 @@
|
|
|
9980
10206
|
case 'notification':
|
|
9981
10207
|
if (msg.data) {
|
|
9982
10208
|
if (msg.data.kind === "update") {
|
|
9983
|
-
|
|
9984
|
-
sendBrowserNotification(
|
|
9985
|
-
"Wand \u53d1\u73b0\u65b0\u7248\u672c",
|
|
9986
|
-
"\u5f53\u524d " + (msg.data.current || "-") + " \u2192 \u6700\u65b0 " + (msg.data.latest || "-"),
|
|
9987
|
-
{ tag: "wand-update" }
|
|
9988
|
-
);
|
|
10209
|
+
notifyUpdateAvailable(msg.data.current || "-", msg.data.latest || "-");
|
|
9989
10210
|
} else if (msg.data.kind === "restart") {
|
|
9990
10211
|
showRestartOverlay();
|
|
9991
10212
|
}
|
|
@@ -11754,7 +11975,7 @@
|
|
|
11754
11975
|
if (msg.role === "thinking") {
|
|
11755
11976
|
var thinkingKey = buildExpandKey("thinking", [getMessageKey(msg, messageIndex), "pty"]);
|
|
11756
11977
|
var thinkingPersisted = getPersistedExpandState(thinkingKey);
|
|
11757
|
-
var thinkingExpanded = thinkingPersisted === null ?
|
|
11978
|
+
var thinkingExpanded = thinkingPersisted === null ? getCardDefault("thinking") : thinkingPersisted;
|
|
11758
11979
|
return '<div class="chat-message thinking">' +
|
|
11759
11980
|
'<div class="thinking-inline thinking-pty ' + (thinkingExpanded ? 'expanded' : 'collapsed') + '" data-expand-kind="thinking" data-expand-key="' + escapeHtml(thinkingKey) + '" data-thinking="" onclick="__thinkingToggle(this)">' +
|
|
11760
11981
|
'<span class="thinking-inline-icon">⦿</span>' +
|
|
@@ -11892,7 +12113,7 @@
|
|
|
11892
12113
|
var summaryText = parts.join(" · ");
|
|
11893
12114
|
var groupKey = buildExpandKey("tool-group", [messageKey, items[0] && items[0].index, items.length]);
|
|
11894
12115
|
var persistedExpanded = getPersistedExpandState(groupKey);
|
|
11895
|
-
var shouldExpand = persistedExpanded === null ?
|
|
12116
|
+
var shouldExpand = persistedExpanded === null ? getCardDefault("toolGroup") : persistedExpanded;
|
|
11896
12117
|
|
|
11897
12118
|
// Render each item's inline-tool card
|
|
11898
12119
|
var innerHtml = "";
|
|
@@ -12004,7 +12225,7 @@
|
|
|
12004
12225
|
}
|
|
12005
12226
|
var thinkingKey = buildExpandKey("thinking", [messageKey, index]);
|
|
12006
12227
|
var thinkingPersisted = getPersistedExpandState(thinkingKey);
|
|
12007
|
-
var thinkingExpanded = thinkingPersisted === null ?
|
|
12228
|
+
var thinkingExpanded = thinkingPersisted === null ? getCardDefault("thinking") : thinkingPersisted;
|
|
12008
12229
|
return '<div class="thinking-inline ' + (thinkingExpanded ? 'expanded' : 'collapsed') + '" data-expand-kind="thinking" data-expand-key="' + escapeHtml(thinkingKey) + '" data-thinking="' + escapeHtml(thinkingText) + '" onclick="__thinkingToggle(this)">' +
|
|
12009
12230
|
'<span class="thinking-inline-icon">⦿</span>' +
|
|
12010
12231
|
'<span class="thinking-inline-preview">' + escapeHtml(thinkingExpanded ? thinkingText : preview) + '</span>' +
|
|
@@ -12101,7 +12322,7 @@
|
|
|
12101
12322
|
var fullResult = resultContent;
|
|
12102
12323
|
|
|
12103
12324
|
var expandedHtml = "";
|
|
12104
|
-
var shouldExpand = persistedExpanded === null ?
|
|
12325
|
+
var shouldExpand = persistedExpanded === null ? getCardDefault("inlineTools") : persistedExpanded;
|
|
12105
12326
|
if (hasResult) {
|
|
12106
12327
|
expandedHtml = '<div class="inline-tool-expanded" style="display: ' + (shouldExpand ? 'block' : 'none') + ';">' +
|
|
12107
12328
|
'<div class="inline-tool-result">' + formatInlineResult(resultContent, toolName) + '</div>' +
|
|
@@ -12186,7 +12407,7 @@
|
|
|
12186
12407
|
|
|
12187
12408
|
// Show command preview in header (truncate long commands)
|
|
12188
12409
|
var cmdPreview = command.length > 80 ? command.slice(0, 77) + "…" : command;
|
|
12189
|
-
var shouldExpand = persistedExpanded === null ?
|
|
12410
|
+
var shouldExpand = persistedExpanded === null ? getCardDefault("terminal") : persistedExpanded;
|
|
12190
12411
|
|
|
12191
12412
|
var termTruncated = toolResult && toolResult._truncated === true;
|
|
12192
12413
|
var termTruncAttrs = termTruncated
|
|
@@ -12275,7 +12496,7 @@
|
|
|
12275
12496
|
// Expand state: respect cardDefaults.editCards and persisted state
|
|
12276
12497
|
var expandKey = buildExpandKey("diff", [messageKey, toolId || index, index]);
|
|
12277
12498
|
var persistedExpanded = getPersistedExpandState(expandKey);
|
|
12278
|
-
var cardDefaultExpand =
|
|
12499
|
+
var cardDefaultExpand = getCardDefault("editCards");
|
|
12279
12500
|
var shouldExpand = persistedExpanded === null ? (statusClass === "diff-pending" || cardDefaultExpand) : persistedExpanded;
|
|
12280
12501
|
var collapsedClass = shouldExpand ? "" : " collapsed";
|
|
12281
12502
|
|
|
@@ -12484,7 +12705,7 @@
|
|
|
12484
12705
|
|
|
12485
12706
|
var expandKey = buildExpandKey("tool-card", [messageKey, toolId]);
|
|
12486
12707
|
var persistedExpanded = getPersistedExpandState(expandKey);
|
|
12487
|
-
var cardDefaultExpand =
|
|
12708
|
+
var cardDefaultExpand = getCardDefault("editCards");
|
|
12488
12709
|
var shouldExpand = persistedExpanded === null ? (statusClass === "loading" || cardDefaultExpand) : persistedExpanded;
|
|
12489
12710
|
var tcTruncated = toolResult && toolResult._truncated === true;
|
|
12490
12711
|
var collapsedClass = shouldExpand ? "" : " collapsed";
|
|
@@ -13041,13 +13262,39 @@
|
|
|
13041
13262
|
}
|
|
13042
13263
|
}
|
|
13043
13264
|
|
|
13265
|
+
function _shouldSendSystemNotification(opts) {
|
|
13266
|
+
var options = opts || {};
|
|
13267
|
+
if (options.onlyWhenHidden && !document.hidden) return false;
|
|
13268
|
+
if (options.skipWhenSelectedSessionId && options.skipWhenSelectedSessionId === state.selectedId && !document.hidden) {
|
|
13269
|
+
return false;
|
|
13270
|
+
}
|
|
13271
|
+
return true;
|
|
13272
|
+
}
|
|
13273
|
+
|
|
13274
|
+
function _isNotificationThrottled(tag, minIntervalMs) {
|
|
13275
|
+
if (!tag || !minIntervalMs || minIntervalMs <= 0) return false;
|
|
13276
|
+
var lastAt = state.notificationHistory[tag] || 0;
|
|
13277
|
+
var now = Date.now();
|
|
13278
|
+
if (now - lastAt < minIntervalMs) return true;
|
|
13279
|
+
state.notificationHistory[tag] = now;
|
|
13280
|
+
return false;
|
|
13281
|
+
}
|
|
13282
|
+
|
|
13044
13283
|
function sendBrowserNotification(title, body, opts) {
|
|
13284
|
+
var options = opts || {};
|
|
13285
|
+
var tag = options.tag || "";
|
|
13286
|
+
if (!_shouldSendSystemNotification(options)) return;
|
|
13287
|
+
if (_isNotificationThrottled(tag, options.minIntervalMs || 0)) return;
|
|
13045
13288
|
// Native Android bridge path
|
|
13046
13289
|
if (_hasNativeBridge) {
|
|
13047
13290
|
var perm = _getNativePermission();
|
|
13048
13291
|
if (perm !== "granted") return;
|
|
13049
13292
|
try {
|
|
13050
|
-
|
|
13293
|
+
var nativeTag = tag;
|
|
13294
|
+
if (options.kind) {
|
|
13295
|
+
nativeTag = options.kind + (tag ? ":" + tag : "");
|
|
13296
|
+
}
|
|
13297
|
+
WandNative.sendNotification(title || "Wand", body || "", nativeTag || "");
|
|
13051
13298
|
} catch (_e) {}
|
|
13052
13299
|
return;
|
|
13053
13300
|
}
|
|
@@ -13057,13 +13304,13 @@
|
|
|
13057
13304
|
try {
|
|
13058
13305
|
var n = new Notification(title, {
|
|
13059
13306
|
body: body || "",
|
|
13060
|
-
icon:
|
|
13061
|
-
tag:
|
|
13307
|
+
icon: options.icon || "/favicon.ico",
|
|
13308
|
+
tag: tag || undefined,
|
|
13062
13309
|
});
|
|
13063
13310
|
n.onclick = function() {
|
|
13064
13311
|
window.focus();
|
|
13065
13312
|
n.close();
|
|
13066
|
-
if (
|
|
13313
|
+
if (options.onClick) options.onClick();
|
|
13067
13314
|
};
|
|
13068
13315
|
// Auto-close after 10s
|
|
13069
13316
|
setTimeout(function() { n.close(); }, 10000);
|
|
@@ -13072,6 +13319,76 @@
|
|
|
13072
13319
|
}
|
|
13073
13320
|
}
|
|
13074
13321
|
|
|
13322
|
+
function notifyTaskProgress(sessionId, task) {
|
|
13323
|
+
if (!task || !task.title) return;
|
|
13324
|
+
var session = state.sessions.find(function(s) { return s.id === sessionId; });
|
|
13325
|
+
if (!session) return;
|
|
13326
|
+
var sessionLabel = session.summary || session.command || sessionId;
|
|
13327
|
+
sendBrowserNotification(
|
|
13328
|
+
"任务进行中",
|
|
13329
|
+
sessionLabel + "\n" + task.title,
|
|
13330
|
+
{
|
|
13331
|
+
kind: "task",
|
|
13332
|
+
tag: "wand-task-" + sessionId + "-" + task.title,
|
|
13333
|
+
minIntervalMs: 90000,
|
|
13334
|
+
onlyWhenHidden: true,
|
|
13335
|
+
skipWhenSelectedSessionId: sessionId,
|
|
13336
|
+
onClick: function() {
|
|
13337
|
+
if (sessionId !== state.selectedId) selectSession(sessionId);
|
|
13338
|
+
}
|
|
13339
|
+
}
|
|
13340
|
+
);
|
|
13341
|
+
}
|
|
13342
|
+
|
|
13343
|
+
function notifyUpdateAvailable(currentVersion, latestVersion) {
|
|
13344
|
+
showUpdateBubble(currentVersion || "-", latestVersion || "-");
|
|
13345
|
+
sendBrowserNotification(
|
|
13346
|
+
"Wand 发现新版本",
|
|
13347
|
+
"当前 " + (currentVersion || "-") + " → 最新 " + (latestVersion || "-"),
|
|
13348
|
+
{
|
|
13349
|
+
kind: "update",
|
|
13350
|
+
tag: "wand-update",
|
|
13351
|
+
minIntervalMs: 300000,
|
|
13352
|
+
}
|
|
13353
|
+
);
|
|
13354
|
+
}
|
|
13355
|
+
|
|
13356
|
+
function notifyPermissionRequest(sessionId, body) {
|
|
13357
|
+
sendBrowserNotification(
|
|
13358
|
+
"需要你的授权",
|
|
13359
|
+
body,
|
|
13360
|
+
{
|
|
13361
|
+
kind: "permission",
|
|
13362
|
+
tag: "wand-perm-" + sessionId,
|
|
13363
|
+
minIntervalMs: 60000,
|
|
13364
|
+
onlyWhenHidden: true,
|
|
13365
|
+
skipWhenSelectedSessionId: sessionId,
|
|
13366
|
+
onClick: function() {
|
|
13367
|
+
if (sessionId !== state.selectedId) {
|
|
13368
|
+
selectSession(sessionId);
|
|
13369
|
+
}
|
|
13370
|
+
}
|
|
13371
|
+
}
|
|
13372
|
+
);
|
|
13373
|
+
}
|
|
13374
|
+
|
|
13375
|
+
function notifyTaskEnded(sessionId, title, body) {
|
|
13376
|
+
sendBrowserNotification(
|
|
13377
|
+
title,
|
|
13378
|
+
body,
|
|
13379
|
+
{
|
|
13380
|
+
kind: "task-ended",
|
|
13381
|
+
tag: "wand-ended-" + sessionId,
|
|
13382
|
+
minIntervalMs: 10000,
|
|
13383
|
+
onClick: function() {
|
|
13384
|
+
if (sessionId !== state.selectedId) selectSession(sessionId);
|
|
13385
|
+
}
|
|
13386
|
+
}
|
|
13387
|
+
);
|
|
13388
|
+
}
|
|
13389
|
+
|
|
13390
|
+
/**
|
|
13391
|
+
|
|
13075
13392
|
/**
|
|
13076
13393
|
* Play a soft, rounded notification chime using Web Audio API.
|
|
13077
13394
|
* Two ascending sine tones with smooth gain envelope — gentle on the ears.
|
|
@@ -13099,13 +13416,15 @@
|
|
|
13099
13416
|
// Some browsers suspend AudioContext until user gesture — resume it
|
|
13100
13417
|
if (ctx.state === "suspended") ctx.resume();
|
|
13101
13418
|
|
|
13419
|
+
var vol = (state.notifVolume || 0) / 100;
|
|
13420
|
+
|
|
13102
13421
|
function tone(freq, start, dur) {
|
|
13103
13422
|
var osc = ctx.createOscillator();
|
|
13104
13423
|
var gain = ctx.createGain();
|
|
13105
13424
|
osc.type = "sine";
|
|
13106
13425
|
osc.frequency.value = freq;
|
|
13107
13426
|
gain.gain.setValueAtTime(0, ctx.currentTime + start);
|
|
13108
|
-
gain.gain.linearRampToValueAtTime(0.
|
|
13427
|
+
gain.gain.linearRampToValueAtTime(0.5 * vol, ctx.currentTime + start + 0.04);
|
|
13109
13428
|
gain.gain.exponentialRampToValueAtTime(0.001, ctx.currentTime + start + dur);
|
|
13110
13429
|
osc.connect(gain);
|
|
13111
13430
|
gain.connect(ctx.destination);
|