@co0ontty/wand 1.14.6 → 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 +419 -127
- package/dist/web-ui/content/styles.css +589 -53
- 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,
|
|
@@ -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);
|
|
@@ -9784,16 +10002,7 @@
|
|
|
9784
10002
|
} else {
|
|
9785
10003
|
endedNotifBody = endedSession ? (endedSession.command || msg.sessionId) : msg.sessionId;
|
|
9786
10004
|
}
|
|
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
|
-
);
|
|
10005
|
+
notifyTaskEnded(msg.sessionId, endedNotifTitle, endedNotifBody);
|
|
9797
10006
|
if (msg.sessionId !== state.selectedId || document.hidden) {
|
|
9798
10007
|
showNotificationBubble({
|
|
9799
10008
|
title: endedNotifTitle,
|
|
@@ -9879,6 +10088,7 @@
|
|
|
9879
10088
|
state.currentTask = msg.data || null;
|
|
9880
10089
|
updateTaskDisplay();
|
|
9881
10090
|
}
|
|
10091
|
+
notifyTaskProgress(msg.sessionId, msg.data || null);
|
|
9882
10092
|
// Update session list to reflect current activity (debounced)
|
|
9883
10093
|
scheduleSessionListUpdate();
|
|
9884
10094
|
break;
|
|
@@ -9925,18 +10135,7 @@
|
|
|
9925
10135
|
} else {
|
|
9926
10136
|
permBody += "\n" + permDetail;
|
|
9927
10137
|
}
|
|
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
|
-
);
|
|
10138
|
+
notifyPermissionRequest(msg.sessionId, permBody);
|
|
9940
10139
|
// In-app bubble if not currently viewing this session
|
|
9941
10140
|
if (msg.sessionId !== state.selectedId) {
|
|
9942
10141
|
showNotificationBubble({
|
|
@@ -9980,12 +10179,7 @@
|
|
|
9980
10179
|
case 'notification':
|
|
9981
10180
|
if (msg.data) {
|
|
9982
10181
|
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
|
-
);
|
|
10182
|
+
notifyUpdateAvailable(msg.data.current || "-", msg.data.latest || "-");
|
|
9989
10183
|
} else if (msg.data.kind === "restart") {
|
|
9990
10184
|
showRestartOverlay();
|
|
9991
10185
|
}
|
|
@@ -11754,7 +11948,7 @@
|
|
|
11754
11948
|
if (msg.role === "thinking") {
|
|
11755
11949
|
var thinkingKey = buildExpandKey("thinking", [getMessageKey(msg, messageIndex), "pty"]);
|
|
11756
11950
|
var thinkingPersisted = getPersistedExpandState(thinkingKey);
|
|
11757
|
-
var thinkingExpanded = thinkingPersisted === null ?
|
|
11951
|
+
var thinkingExpanded = thinkingPersisted === null ? getCardDefault("thinking") : thinkingPersisted;
|
|
11758
11952
|
return '<div class="chat-message thinking">' +
|
|
11759
11953
|
'<div class="thinking-inline thinking-pty ' + (thinkingExpanded ? 'expanded' : 'collapsed') + '" data-expand-kind="thinking" data-expand-key="' + escapeHtml(thinkingKey) + '" data-thinking="" onclick="__thinkingToggle(this)">' +
|
|
11760
11954
|
'<span class="thinking-inline-icon">⦿</span>' +
|
|
@@ -11892,7 +12086,7 @@
|
|
|
11892
12086
|
var summaryText = parts.join(" · ");
|
|
11893
12087
|
var groupKey = buildExpandKey("tool-group", [messageKey, items[0] && items[0].index, items.length]);
|
|
11894
12088
|
var persistedExpanded = getPersistedExpandState(groupKey);
|
|
11895
|
-
var shouldExpand = persistedExpanded === null ?
|
|
12089
|
+
var shouldExpand = persistedExpanded === null ? getCardDefault("toolGroup") : persistedExpanded;
|
|
11896
12090
|
|
|
11897
12091
|
// Render each item's inline-tool card
|
|
11898
12092
|
var innerHtml = "";
|
|
@@ -12004,7 +12198,7 @@
|
|
|
12004
12198
|
}
|
|
12005
12199
|
var thinkingKey = buildExpandKey("thinking", [messageKey, index]);
|
|
12006
12200
|
var thinkingPersisted = getPersistedExpandState(thinkingKey);
|
|
12007
|
-
var thinkingExpanded = thinkingPersisted === null ?
|
|
12201
|
+
var thinkingExpanded = thinkingPersisted === null ? getCardDefault("thinking") : thinkingPersisted;
|
|
12008
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)">' +
|
|
12009
12203
|
'<span class="thinking-inline-icon">⦿</span>' +
|
|
12010
12204
|
'<span class="thinking-inline-preview">' + escapeHtml(thinkingExpanded ? thinkingText : preview) + '</span>' +
|
|
@@ -12101,7 +12295,7 @@
|
|
|
12101
12295
|
var fullResult = resultContent;
|
|
12102
12296
|
|
|
12103
12297
|
var expandedHtml = "";
|
|
12104
|
-
var shouldExpand = persistedExpanded === null ?
|
|
12298
|
+
var shouldExpand = persistedExpanded === null ? getCardDefault("inlineTools") : persistedExpanded;
|
|
12105
12299
|
if (hasResult) {
|
|
12106
12300
|
expandedHtml = '<div class="inline-tool-expanded" style="display: ' + (shouldExpand ? 'block' : 'none') + ';">' +
|
|
12107
12301
|
'<div class="inline-tool-result">' + formatInlineResult(resultContent, toolName) + '</div>' +
|
|
@@ -12186,7 +12380,7 @@
|
|
|
12186
12380
|
|
|
12187
12381
|
// Show command preview in header (truncate long commands)
|
|
12188
12382
|
var cmdPreview = command.length > 80 ? command.slice(0, 77) + "…" : command;
|
|
12189
|
-
var shouldExpand = persistedExpanded === null ?
|
|
12383
|
+
var shouldExpand = persistedExpanded === null ? getCardDefault("terminal") : persistedExpanded;
|
|
12190
12384
|
|
|
12191
12385
|
var termTruncated = toolResult && toolResult._truncated === true;
|
|
12192
12386
|
var termTruncAttrs = termTruncated
|
|
@@ -12275,7 +12469,7 @@
|
|
|
12275
12469
|
// Expand state: respect cardDefaults.editCards and persisted state
|
|
12276
12470
|
var expandKey = buildExpandKey("diff", [messageKey, toolId || index, index]);
|
|
12277
12471
|
var persistedExpanded = getPersistedExpandState(expandKey);
|
|
12278
|
-
var cardDefaultExpand =
|
|
12472
|
+
var cardDefaultExpand = getCardDefault("editCards");
|
|
12279
12473
|
var shouldExpand = persistedExpanded === null ? (statusClass === "diff-pending" || cardDefaultExpand) : persistedExpanded;
|
|
12280
12474
|
var collapsedClass = shouldExpand ? "" : " collapsed";
|
|
12281
12475
|
|
|
@@ -12484,7 +12678,7 @@
|
|
|
12484
12678
|
|
|
12485
12679
|
var expandKey = buildExpandKey("tool-card", [messageKey, toolId]);
|
|
12486
12680
|
var persistedExpanded = getPersistedExpandState(expandKey);
|
|
12487
|
-
var cardDefaultExpand =
|
|
12681
|
+
var cardDefaultExpand = getCardDefault("editCards");
|
|
12488
12682
|
var shouldExpand = persistedExpanded === null ? (statusClass === "loading" || cardDefaultExpand) : persistedExpanded;
|
|
12489
12683
|
var tcTruncated = toolResult && toolResult._truncated === true;
|
|
12490
12684
|
var collapsedClass = shouldExpand ? "" : " collapsed";
|
|
@@ -13041,13 +13235,39 @@
|
|
|
13041
13235
|
}
|
|
13042
13236
|
}
|
|
13043
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
|
+
|
|
13044
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;
|
|
13045
13261
|
// Native Android bridge path
|
|
13046
13262
|
if (_hasNativeBridge) {
|
|
13047
13263
|
var perm = _getNativePermission();
|
|
13048
13264
|
if (perm !== "granted") return;
|
|
13049
13265
|
try {
|
|
13050
|
-
|
|
13266
|
+
var nativeTag = tag;
|
|
13267
|
+
if (options.kind) {
|
|
13268
|
+
nativeTag = options.kind + (tag ? ":" + tag : "");
|
|
13269
|
+
}
|
|
13270
|
+
WandNative.sendNotification(title || "Wand", body || "", nativeTag || "");
|
|
13051
13271
|
} catch (_e) {}
|
|
13052
13272
|
return;
|
|
13053
13273
|
}
|
|
@@ -13057,13 +13277,13 @@
|
|
|
13057
13277
|
try {
|
|
13058
13278
|
var n = new Notification(title, {
|
|
13059
13279
|
body: body || "",
|
|
13060
|
-
icon:
|
|
13061
|
-
tag:
|
|
13280
|
+
icon: options.icon || "/favicon.ico",
|
|
13281
|
+
tag: tag || undefined,
|
|
13062
13282
|
});
|
|
13063
13283
|
n.onclick = function() {
|
|
13064
13284
|
window.focus();
|
|
13065
13285
|
n.close();
|
|
13066
|
-
if (
|
|
13286
|
+
if (options.onClick) options.onClick();
|
|
13067
13287
|
};
|
|
13068
13288
|
// Auto-close after 10s
|
|
13069
13289
|
setTimeout(function() { n.close(); }, 10000);
|
|
@@ -13072,6 +13292,76 @@
|
|
|
13072
13292
|
}
|
|
13073
13293
|
}
|
|
13074
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
|
+
|
|
13075
13365
|
/**
|
|
13076
13366
|
* Play a soft, rounded notification chime using Web Audio API.
|
|
13077
13367
|
* Two ascending sine tones with smooth gain envelope — gentle on the ears.
|
|
@@ -13099,13 +13389,15 @@
|
|
|
13099
13389
|
// Some browsers suspend AudioContext until user gesture — resume it
|
|
13100
13390
|
if (ctx.state === "suspended") ctx.resume();
|
|
13101
13391
|
|
|
13392
|
+
var vol = (state.notifVolume || 0) / 100;
|
|
13393
|
+
|
|
13102
13394
|
function tone(freq, start, dur) {
|
|
13103
13395
|
var osc = ctx.createOscillator();
|
|
13104
13396
|
var gain = ctx.createGain();
|
|
13105
13397
|
osc.type = "sine";
|
|
13106
13398
|
osc.frequency.value = freq;
|
|
13107
13399
|
gain.gain.setValueAtTime(0, ctx.currentTime + start);
|
|
13108
|
-
gain.gain.linearRampToValueAtTime(0.
|
|
13400
|
+
gain.gain.linearRampToValueAtTime(0.5 * vol, ctx.currentTime + start + 0.04);
|
|
13109
13401
|
gain.gain.exponentialRampToValueAtTime(0.001, ctx.currentTime + start + dur);
|
|
13110
13402
|
osc.connect(gain);
|
|
13111
13403
|
gain.connect(ctx.destination);
|