@co0ontty/wand 1.31.2 → 1.32.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/auth.d.ts +20 -0
- package/dist/auth.js +31 -0
- package/dist/cert.d.ts +17 -2
- package/dist/cert.js +124 -68
- package/dist/config.js +12 -0
- package/dist/server.js +60 -51
- package/dist/tui/commands.js +51 -5
- package/dist/types.d.ts +9 -0
- package/dist/web-ui/content/scripts.js +271 -279
- package/dist/web-ui/content/styles.css +291 -308
- package/dist/ws-broadcast.d.ts +2 -2
- package/dist/ws-broadcast.js +5 -10
- package/package.json +1 -1
|
@@ -1,22 +1,28 @@
|
|
|
1
1
|
// Register Service Worker for PWA
|
|
2
|
-
//
|
|
2
|
+
// 自签证书场景下 SW 注册会被浏览器强拒(规范要求 secure context + 证书可信,
|
|
3
|
+
// 即便用户已"高级 → 继续访问"也不行)。这里只能优雅降级,并把解决路径打到 console。
|
|
3
4
|
if ('serviceWorker' in navigator) {
|
|
4
|
-
// First, try to fetch the service worker script with a custom handler for certificate errors
|
|
5
5
|
fetch('/sw.js', { cache: 'no-cache' })
|
|
6
6
|
.then(function(response) {
|
|
7
7
|
if (response.ok) {
|
|
8
8
|
return navigator.serviceWorker.register('/sw.js');
|
|
9
9
|
}
|
|
10
|
-
// If fetch fails (e.g., certificate error), skip service worker registration
|
|
11
10
|
console.log('SW fetch failed, skipping service worker registration');
|
|
12
11
|
return Promise.reject('Service worker script not available');
|
|
13
12
|
})
|
|
14
13
|
.catch(function(e) {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
14
|
+
var msg = (e && e.message) || String(e || '');
|
|
15
|
+
var isCertIssue = (e && e.name === 'TypeError') || /certificate|SSL|ERR_CERT/i.test(msg);
|
|
16
|
+
if (isCertIssue && location.protocol === 'https:') {
|
|
17
|
+
console.warn(
|
|
18
|
+
'[wand] PWA / Service Worker 因 TLS 证书不可信而跳过。\n' +
|
|
19
|
+
'解决办法(任选一种):\n' +
|
|
20
|
+
' 1) 从 ' + location.origin + '/cert/server.crt 下载本机自签证书,导入到系统/浏览器"受信任根证书颁发机构"\n' +
|
|
21
|
+
' 2) 在本机用 mkcert 签发受信任证书,并在 ~/.wand/config.json 配置 tls.certPath / tls.keyPath\n' +
|
|
22
|
+
' 3) 用内网 CA 或 Let\'s Encrypt 给域名签真证书(同上配置 tls)'
|
|
23
|
+
);
|
|
18
24
|
} else {
|
|
19
|
-
console.log('SW registration failed:',
|
|
25
|
+
console.log('SW registration failed:', msg);
|
|
20
26
|
}
|
|
21
27
|
});
|
|
22
28
|
|
|
@@ -137,11 +143,9 @@
|
|
|
137
143
|
})(), // 跨会话排队消息 [{ id, text, cwd, mode, tool }]
|
|
138
144
|
structuredInputQueue: [], // 结构化会话同会话排队消息
|
|
139
145
|
// 排队条 UI 局部状态 ——
|
|
140
|
-
//
|
|
141
|
-
// queueBarItemExpanded: 展开面板里被点开看完整内容的 item 下标集合
|
|
146
|
+
// queueBarHoverIndex: 当前被鼠标悬停的气泡下标(null 时默认展开队首)
|
|
142
147
|
// queueBarDrag: 拖拽排序进行中时的临时状态(pointer 捕获、起始坐标、参考 rect)
|
|
143
|
-
|
|
144
|
-
queueBarItemExpanded: {},
|
|
148
|
+
queueBarHoverIndex: null,
|
|
145
149
|
queueBarDrag: null,
|
|
146
150
|
drafts: {},
|
|
147
151
|
isSyncingInputBox: false,
|
|
@@ -293,9 +297,6 @@
|
|
|
293
297
|
fileExplorerCwd: "",
|
|
294
298
|
fileExplorerTruncated: false,
|
|
295
299
|
fileExplorerTotal: 0,
|
|
296
|
-
fileExplorerShowHidden: (function() {
|
|
297
|
-
try { return localStorage.getItem("wand-file-show-hidden") === "1"; } catch (e) { return false; }
|
|
298
|
-
})(),
|
|
299
300
|
claudeHistory: [],
|
|
300
301
|
claudeHistoryLoaded: false,
|
|
301
302
|
claudeHistoryExpanded: true,
|
|
@@ -318,6 +319,59 @@
|
|
|
318
319
|
})()
|
|
319
320
|
};
|
|
320
321
|
|
|
322
|
+
// ── 统一线性图标库 ──
|
|
323
|
+
// 替代页面里散落的 emoji(🛡 / ⌨ / 📁 / 🔔 …)。这些 emoji 在系统字体里渲染成
|
|
324
|
+
// 彩色卡通形态,与项目温暖米色 + 棕橙的复古主题视觉冲突明显。这里集中维护
|
|
325
|
+
// currentColor 线性 SVG,让图标跟随父级文字颜色变化,hover / active 状态自然继承。
|
|
326
|
+
var ICON_PATHS = {
|
|
327
|
+
// shape sets — 24x24 viewbox, currentColor stroke
|
|
328
|
+
shield: '<path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/>',
|
|
329
|
+
shieldCheck: '<path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/><polyline points="9 12 11 14 15 10"/>',
|
|
330
|
+
keyboard: '<rect x="2" y="6" width="20" height="12" rx="2"/><line x1="6" y1="10" x2="6" y2="10"/><line x1="10" y1="10" x2="10" y2="10"/><line x1="14" y1="10" x2="14" y2="10"/><line x1="18" y1="10" x2="18" y2="10"/><line x1="6" y1="14" x2="6" y2="14"/><line x1="18" y1="14" x2="18" y2="14"/><line x1="9" y1="14" x2="15" y2="14"/>',
|
|
331
|
+
cloud: '<path d="M17.5 19a4.5 4.5 0 1 0-1-8.9 6 6 0 0 0-11.5 1.7A4 4 0 0 0 6 19h11.5z"/>',
|
|
332
|
+
terminal: '<polyline points="4 7 9 12 4 17"/><line x1="12" y1="17" x2="20" y2="17"/>',
|
|
333
|
+
chat: '<path d="M21 12a8 8 0 0 1-12.9 6.3L3 20l1.7-5.1A8 8 0 1 1 21 12z"/>',
|
|
334
|
+
folder: '<path d="M3 7a2 2 0 0 1 2-2h4l2 2h8a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/>',
|
|
335
|
+
folderOpen:'<path d="M3 7a2 2 0 0 1 2-2h4l2 2h8a2 2 0 0 1 2 2"/><path d="M3 9h18l-2 8a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/>',
|
|
336
|
+
trash: '<polyline points="3 6 5 6 21 6"/><path d="M19 6l-1 13a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2L5 6"/><path d="M10 11v6"/><path d="M14 11v6"/><path d="M9 6V4a1 1 0 0 1 1-1h4a1 1 0 0 1 1 1v2"/>',
|
|
337
|
+
slash: '<polyline points="4 17 10 11 4 5"/><line x1="12" y1="19" x2="20" y2="19"/>',
|
|
338
|
+
chevronDown: '<polyline points="6 9 12 15 18 9"/>',
|
|
339
|
+
chevronUp: '<polyline points="6 15 12 9 18 15"/>',
|
|
340
|
+
chevronRight:'<polyline points="9 6 15 12 9 18"/>',
|
|
341
|
+
bell: '<path d="M18 16v-5a6 6 0 1 0-12 0v5l-2 2h16z"/><path d="M10 21a2 2 0 0 0 4 0"/>',
|
|
342
|
+
music: '<path d="M9 18V5l12-2v13"/><circle cx="6" cy="18" r="3"/><circle cx="18" cy="16" r="3"/>',
|
|
343
|
+
vibrate: '<rect x="9" y="4" width="6" height="16" rx="1"/><path d="M5 8v8"/><path d="M3 10v4"/><path d="M19 8v8"/><path d="M21 10v4"/>',
|
|
344
|
+
globe: '<circle cx="12" cy="12" r="9"/><line x1="3" y1="12" x2="21" y2="12"/><path d="M12 3a14 14 0 0 1 0 18"/><path d="M12 3a14 14 0 0 0 0 18"/>',
|
|
345
|
+
smartphone:'<rect x="6" y="2" width="12" height="20" rx="2.5"/><line x1="11" y1="18" x2="13" y2="18"/>',
|
|
346
|
+
desktop: '<rect x="3" y="4" width="18" height="12" rx="2"/><line x1="8" y1="20" x2="16" y2="20"/><line x1="12" y1="16" x2="12" y2="20"/>',
|
|
347
|
+
link: '<path d="M10 14a4.5 4.5 0 0 0 6.36 0l3-3a4.5 4.5 0 1 0-6.36-6.36l-1.42 1.41"/><path d="M14 10a4.5 4.5 0 0 0-6.36 0l-3 3a4.5 4.5 0 1 0 6.36 6.36l1.42-1.41"/>',
|
|
348
|
+
palette: '<circle cx="13.5" cy="6.5" r="1"/><circle cx="17.5" cy="10.5" r="1"/><circle cx="8.5" cy="7.5" r="1"/><circle cx="6.5" cy="12.5" r="1"/><path d="M12 3a9 9 0 1 0 0 18 1.5 1.5 0 0 0 1.1-2.5 1.5 1.5 0 0 1 1.1-2.5h2.3A4.5 4.5 0 0 0 21 11.5C21 6.8 16.97 3 12 3z"/>',
|
|
349
|
+
play: '<polygon points="6 4 20 12 6 20 6 4"/>',
|
|
350
|
+
inbox: '<polyline points="22 13 16 13 14 16 10 16 8 13 2 13"/><path d="M5 5h14l3 8v6a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2v-6z"/>',
|
|
351
|
+
zap: '<polygon points="13 2 4 14 11 14 10 22 20 9 13 9 13 2"/>',
|
|
352
|
+
wrench: '<path d="M14.7 6.3a4 4 0 1 1 4 4l-9 9-3.5 1 1-3.5 7.5-7.5z"/>',
|
|
353
|
+
edit: '<path d="M12 20h9"/><path d="M16.5 3.5a2.12 2.12 0 0 1 3 3L7 19l-4 1 1-4z"/>',
|
|
354
|
+
check: '<polyline points="5 12 10 17 19 7"/>',
|
|
355
|
+
signal: '<path d="M2 12a15 15 0 0 1 20 0"/><path d="M5 16a10 10 0 0 1 14 0"/><path d="M9 20a4 4 0 0 1 6 0"/><circle cx="12" cy="20" r="0.5" fill="currentColor"/>',
|
|
356
|
+
file: '<path d="M14 3H6a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V9z"/><polyline points="14 3 14 9 20 9"/>',
|
|
357
|
+
image: '<rect x="3" y="4" width="18" height="16" rx="2"/><circle cx="9" cy="10" r="1.5"/><polyline points="3 18 9 12 14 17 21 12"/>',
|
|
358
|
+
sigma: '<polyline points="18 4 6 4 13 12 6 20 18 20"/>'
|
|
359
|
+
};
|
|
360
|
+
// 渲染 SVG 字符串。size 默认 14,strokeWidth 默认 1.8(与现有 send/stop 按钮线宽接近)。
|
|
361
|
+
// cls 用于添加额外 class(如 .composer-pill-icon),便于 CSS 微调。
|
|
362
|
+
function iconSvg(name, opts) {
|
|
363
|
+
var path = ICON_PATHS[name];
|
|
364
|
+
if (!path) return "";
|
|
365
|
+
opts = opts || {};
|
|
366
|
+
var size = opts.size || 14;
|
|
367
|
+
var stroke = opts.strokeWidth || 1.8;
|
|
368
|
+
var cls = opts.cls ? ' class="' + opts.cls + '"' : "";
|
|
369
|
+
var fill = opts.fill || "none";
|
|
370
|
+
return '<svg' + cls + ' width="' + size + '" height="' + size + '" viewBox="0 0 24 24"' +
|
|
371
|
+
' fill="' + fill + '" stroke="currentColor" stroke-width="' + stroke + '"' +
|
|
372
|
+
' stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">' + path + '</svg>';
|
|
373
|
+
}
|
|
374
|
+
|
|
321
375
|
// ── Structured session status bar (in-flight timer) ──
|
|
322
376
|
var _statusBarTimerId = null;
|
|
323
377
|
var _statusBarStartTime = 0;
|
|
@@ -1313,7 +1367,7 @@
|
|
|
1313
1367
|
app.innerHTML =
|
|
1314
1368
|
'<div class="boot-loading">' +
|
|
1315
1369
|
'<div class="boot-loading-card">' +
|
|
1316
|
-
'<div class="boot-loading-text" style="font-size:1.3em;margin-bottom:12px"
|
|
1370
|
+
'<div class="boot-loading-text" style="font-size:1.3em;margin-bottom:12px;display:flex;align-items:center;justify-content:center;gap:8px">' + iconSvg("signal", { size: 20, strokeWidth: 1.8 }) + '<span>无法连接到服务器</span></div>' +
|
|
1317
1371
|
'<div class="boot-loading-text" style="opacity:0.7;font-size:0.95em">请检查网络连接或确认 Wand 服务正在运行。</div>' +
|
|
1318
1372
|
'<button onclick="location.reload()" style="margin-top:18px;padding:8px 24px;border-radius:8px;border:1px solid rgba(150,118,85,0.3);background:rgba(255,255,255,0.8);cursor:pointer;font-size:0.95em">重试</button>' +
|
|
1319
1373
|
'</div>' +
|
|
@@ -1418,10 +1472,10 @@
|
|
|
1418
1472
|
'</span>' +
|
|
1419
1473
|
'<span class="approval-stats-popup" id="approval-stats-popup">' +
|
|
1420
1474
|
'<span class="approval-stats-popup-title">自动批准统计</span>' +
|
|
1421
|
-
(stats.command > 0 ? '<span class="approval-stats-row"><span class="approval-stats-row-icon"
|
|
1422
|
-
(stats.file > 0 ? '<span class="approval-stats-row"><span class="approval-stats-row-icon"
|
|
1423
|
-
(stats.tool > 0 ? '<span class="approval-stats-row"><span class="approval-stats-row-icon"
|
|
1424
|
-
'<span class="approval-stats-row approval-stats-row-total"><span class="approval-stats-row-icon"
|
|
1475
|
+
(stats.command > 0 ? '<span class="approval-stats-row"><span class="approval-stats-row-icon">' + iconSvg("terminal", { size: 12, strokeWidth: 1.8 }) + '</span><span class="approval-stats-row-label">命令执行</span><span class="approval-stats-row-count">' + stats.command + '</span></span>' : '') +
|
|
1476
|
+
(stats.file > 0 ? '<span class="approval-stats-row"><span class="approval-stats-row-icon">' + iconSvg("edit", { size: 12, strokeWidth: 1.8 }) + '</span><span class="approval-stats-row-label">文件写入</span><span class="approval-stats-row-count">' + stats.file + '</span></span>' : '') +
|
|
1477
|
+
(stats.tool > 0 ? '<span class="approval-stats-row"><span class="approval-stats-row-icon">' + iconSvg("wrench", { size: 12, strokeWidth: 1.8 }) + '</span><span class="approval-stats-row-label">其他工具</span><span class="approval-stats-row-count">' + stats.tool + '</span></span>' : '') +
|
|
1478
|
+
'<span class="approval-stats-row approval-stats-row-total"><span class="approval-stats-row-icon">' + iconSvg("sigma", { size: 12, strokeWidth: 1.8 }) + '</span><span class="approval-stats-row-label">合计</span><span class="approval-stats-row-count">' + stats.total + '</span></span>' +
|
|
1425
1479
|
'</span>' +
|
|
1426
1480
|
'</span>';
|
|
1427
1481
|
}
|
|
@@ -1633,12 +1687,6 @@
|
|
|
1633
1687
|
'<span class="file-side-panel-title">文件</span>' +
|
|
1634
1688
|
'</div>' +
|
|
1635
1689
|
'<div class="file-side-panel-header-actions">' +
|
|
1636
|
-
'<button class="file-side-panel-iconbtn file-explorer-toggle-hidden' +
|
|
1637
|
-
(state.fileExplorerShowHidden ? ' active' : '') + '" id="file-explorer-toggle-hidden" type="button" title="' +
|
|
1638
|
-
(state.fileExplorerShowHidden ? "隐藏点开头文件" : "显示隐藏文件") + '" aria-pressed="' +
|
|
1639
|
-
(state.fileExplorerShowHidden ? "true" : "false") + '" aria-label="切换显示隐藏文件">' +
|
|
1640
|
-
wandFileIcon(state.fileExplorerShowHidden ? "eye" : "eye-off", { size: 15 }) +
|
|
1641
|
-
'</button>' +
|
|
1642
1690
|
'<button class="file-side-panel-iconbtn" id="file-explorer-refresh" type="button" title="刷新" aria-label="刷新文件列表">' +
|
|
1643
1691
|
wandFileIcon("refresh", { size: 15 }) +
|
|
1644
1692
|
'</button>' +
|
|
@@ -1687,26 +1735,30 @@
|
|
|
1687
1735
|
'<p class="blank-chat-subtitle">支持终端 PTY 会话与结构化 chat 会话,两种模式可并存。</p>' +
|
|
1688
1736
|
'<div class="blank-chat-tools">' +
|
|
1689
1737
|
'<button class="blank-chat-tool-btn" id="welcome-tool-claude" type="button">' +
|
|
1690
|
-
'<span class="tool-icon"
|
|
1738
|
+
'<span class="tool-icon">' + iconSvg("terminal", { size: 16, strokeWidth: 1.8 }) + '</span>新建终端会话' +
|
|
1691
1739
|
'</button>' +
|
|
1692
1740
|
'<button class="blank-chat-tool-btn" id="welcome-tool-codex" type="button">' +
|
|
1693
|
-
'<span class="tool-icon">⌘</span>新建 Codex 会话' +
|
|
1741
|
+
'<span class="tool-icon tool-icon-text">⌘</span>新建 Codex 会话' +
|
|
1694
1742
|
'</button>' +
|
|
1695
1743
|
'<button class="blank-chat-tool-btn" id="welcome-tool-structured" type="button">' +
|
|
1696
|
-
'<span class="tool-icon"
|
|
1744
|
+
'<span class="tool-icon">' + iconSvg("chat", { size: 16, strokeWidth: 1.8 }) + '</span>新建结构化会话' +
|
|
1697
1745
|
'</button>' +
|
|
1698
1746
|
'</div>' +
|
|
1699
1747
|
'<div class="blank-chat-cwd-wrap">' +
|
|
1700
1748
|
'<div class="blank-chat-cwd" id="blank-chat-cwd" role="button" tabindex="0" title="点击切换工作目录">' +
|
|
1701
|
-
'<span class="blank-chat-cwd-icon"
|
|
1749
|
+
'<span class="blank-chat-cwd-icon">' + iconSvg("folder", { size: 13, strokeWidth: 1.8 }) + '</span>' +
|
|
1702
1750
|
'<span class="blank-chat-cwd-path" id="blank-chat-cwd-path">' + escapeHtml(getEffectiveCwd()) + '</span>' +
|
|
1703
|
-
'<span class="blank-chat-cwd-arrow" id="blank-chat-cwd-arrow"
|
|
1751
|
+
'<span class="blank-chat-cwd-arrow" id="blank-chat-cwd-arrow">' + iconSvg("chevronDown", { size: 11, strokeWidth: 2 }) + '</span>' +
|
|
1704
1752
|
'</div>' +
|
|
1705
1753
|
'<div class="blank-chat-cwd-dropdown hidden" id="blank-chat-cwd-dropdown"></div>' +
|
|
1706
1754
|
'</div>' +
|
|
1707
1755
|
'</div>' +
|
|
1708
1756
|
'</div>' +
|
|
1709
1757
|
'<div class="input-panel' + (state.selectedId ? "" : " hidden") + '">' +
|
|
1758
|
+
// 排队气泡宿主:默认 display:none,updateQueueBar() 在 queuedMessages 非空时
|
|
1759
|
+
// 显形。位置在 composer-top-row(含 "回复中" 状态条)之上,对话框右下角,
|
|
1760
|
+
// 不进入输入框内部。所有内容由 updater 注入;这里只保留稳定的外层骨架。
|
|
1761
|
+
'<div id="queue-bar-host" class="queue-bar-host" hidden></div>' +
|
|
1710
1762
|
'<div class="composer-top-row">' +
|
|
1711
1763
|
'<div id="todo-progress" class="todo-progress hidden">' +
|
|
1712
1764
|
'<div class="todo-progress-header" id="todo-progress-toggle">' +
|
|
@@ -1727,11 +1779,6 @@
|
|
|
1727
1779
|
'<ul class="todo-progress-list" id="todo-progress-list"></ul>' +
|
|
1728
1780
|
'</div>' +
|
|
1729
1781
|
'</div>' +
|
|
1730
|
-
// 排队条宿主:默认 display:none,updateQueueBar() 在 queuedMessages 非空时
|
|
1731
|
-
// 显形。结构上夹在 composer-top-row(todo 进度)和 input-composer(输入框 +
|
|
1732
|
-
// 工具栏)之间,位置正好"在输入框上方、对话框右下角"。所有内容由 updater
|
|
1733
|
-
// 注入;这里只保留稳定的外层骨架,便于 renderAppShell 全量重建后无缝复位。
|
|
1734
|
-
'<div id="queue-bar-host" class="queue-bar-host" hidden></div>' +
|
|
1735
1782
|
'<div class="input-composer">' +
|
|
1736
1783
|
'<button id="prompt-optimize-btn" class="prompt-optimize-btn" type="button" title="提示词优化(AI)" aria-label="提示词优化">' +
|
|
1737
1784
|
'<svg class="prompt-optimize-icon" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">' +
|
|
@@ -1778,7 +1825,7 @@
|
|
|
1778
1825
|
'</span>' +
|
|
1779
1826
|
'</span>' +
|
|
1780
1827
|
renderAutoApproveChip(selectedSession) +
|
|
1781
|
-
'<button id="terminal-interactive-toggle-top" class="composer-pill composer-pill-chip composer-interactive-toggle' + (state.terminalInteractive ? " active" : "") + '" type="button" title="切换终端交互模式"
|
|
1828
|
+
'<button id="terminal-interactive-toggle-top" class="composer-pill composer-pill-chip composer-interactive-toggle' + (state.terminalInteractive ? " active" : "") + '" type="button" title="切换终端交互模式" aria-label="切换终端交互模式">' + iconSvg("keyboard", { size: 13, strokeWidth: 1.7, cls: "composer-pill-icon" }) + '</button>' +
|
|
1782
1829
|
'<span class="permission-actions hidden" id="permission-actions">' +
|
|
1783
1830
|
'<span class="permission-actions-divider"></span>' +
|
|
1784
1831
|
'<span class="permission-actions-label" id="permission-actions-label">等待授权</span>' +
|
|
@@ -1813,7 +1860,7 @@
|
|
|
1813
1860
|
? (function() {
|
|
1814
1861
|
var bits = "";
|
|
1815
1862
|
if (selectedSession.provider === "claude" && selectedSession.claudeSessionId) {
|
|
1816
|
-
bits += '<span id="claude-session-id-badge" class="claude-session-id-badge" data-claude-id="' + escapeHtml(selectedSession.claudeSessionId) + '" title="点击复制 Claude 会话 ID"
|
|
1863
|
+
bits += '<span id="claude-session-id-badge" class="claude-session-id-badge" data-claude-id="' + escapeHtml(selectedSession.claudeSessionId) + '" title="点击复制 Claude 会话 ID">' + iconSvg("cloud", { size: 11, strokeWidth: 1.7, cls: "claude-session-id-icon" }) + '<span class="claude-session-id-text">' + escapeHtml(selectedSession.claudeSessionId.slice(0, 8)) + '</span></span>';
|
|
1817
1864
|
}
|
|
1818
1865
|
if (!isStructuredSession(selectedSession) && selectedSession.exitCode !== undefined && selectedSession.exitCode !== null) {
|
|
1819
1866
|
if (bits) bits += '<span class="session-info-separator">|</span>';
|
|
@@ -1834,12 +1881,12 @@
|
|
|
1834
1881
|
'</div>' +
|
|
1835
1882
|
'<div class="modal-body">' +
|
|
1836
1883
|
'<div class="folder-picker-quick-row">' +
|
|
1837
|
-
'<button class="folder-picker-quick-btn" data-path="/tmp"
|
|
1838
|
-
'<button class="folder-picker-quick-btn" data-path="/"
|
|
1884
|
+
'<button class="folder-picker-quick-btn btn-with-icon" data-path="/tmp">' + iconSvg("trash", { size: 13, strokeWidth: 1.7 }) + '<span>临时目录</span></button>' +
|
|
1885
|
+
'<button class="folder-picker-quick-btn btn-with-icon" data-path="/">' + iconSvg("folder", { size: 13, strokeWidth: 1.7 }) + '<span>根目录</span></button>' +
|
|
1839
1886
|
'</div>' +
|
|
1840
1887
|
'<div id="folder-breadcrumb" class="folder-breadcrumb"></div>' +
|
|
1841
1888
|
'<div class="folder-picker">' +
|
|
1842
|
-
'<span class="folder-picker-icon"
|
|
1889
|
+
'<span class="folder-picker-icon">' + iconSvg("folder", { size: 15, strokeWidth: 1.7 }) + '</span>' +
|
|
1843
1890
|
'<input type="text" id="folder-picker-input" class="folder-picker-input" value="" placeholder="输入或选择工作目录..." autocomplete="off" />' +
|
|
1844
1891
|
'</div>' +
|
|
1845
1892
|
'<div id="folder-picker-dropdown" class="folder-picker-dropdown hidden"></div>' +
|
|
@@ -2751,7 +2798,7 @@
|
|
|
2751
2798
|
'</div>' +
|
|
2752
2799
|
'<div class="settings-update-section" id="web-update-section">' +
|
|
2753
2800
|
'<div class="settings-section-head">' +
|
|
2754
|
-
'<span class="settings-section-icon"
|
|
2801
|
+
'<span class="settings-section-icon">' + iconSvg("globe", { size: 18, strokeWidth: 1.7 }) + '</span>' +
|
|
2755
2802
|
'<div class="settings-section-head-text">' +
|
|
2756
2803
|
'<h4 class="settings-section-heading">Web 端</h4>' +
|
|
2757
2804
|
'<p class="settings-section-sub">浏览器访问的服务版本</p>' +
|
|
@@ -2780,7 +2827,7 @@
|
|
|
2780
2827
|
'</div>' +
|
|
2781
2828
|
'<div class="settings-update-section hidden" id="android-apk-section">' +
|
|
2782
2829
|
'<div class="settings-section-head">' +
|
|
2783
|
-
'<span class="settings-section-icon"
|
|
2830
|
+
'<span class="settings-section-icon">' + iconSvg("smartphone", { size: 18, strokeWidth: 1.7 }) + '</span>' +
|
|
2784
2831
|
'<div class="settings-section-head-text">' +
|
|
2785
2832
|
'<h4 class="settings-section-heading">Android App</h4>' +
|
|
2786
2833
|
'<p class="settings-section-sub">原生客户端版本与 APK 下载</p>' +
|
|
@@ -2814,7 +2861,7 @@
|
|
|
2814
2861
|
'</div>' +
|
|
2815
2862
|
'<div class="settings-update-section hidden" id="macos-dmg-section">' +
|
|
2816
2863
|
'<div class="settings-section-head">' +
|
|
2817
|
-
'<span class="settings-section-icon"
|
|
2864
|
+
'<span class="settings-section-icon">' + iconSvg("desktop", { size: 18, strokeWidth: 1.7 }) + '</span>' +
|
|
2818
2865
|
'<div class="settings-section-head-text">' +
|
|
2819
2866
|
'<h4 class="settings-section-heading">macOS App</h4>' +
|
|
2820
2867
|
'<p class="settings-section-sub">原生客户端版本与 DMG 下载</p>' +
|
|
@@ -2848,7 +2895,7 @@
|
|
|
2848
2895
|
'</div>' +
|
|
2849
2896
|
'<div class="settings-update-section" id="android-connect-section">' +
|
|
2850
2897
|
'<div class="settings-section-head">' +
|
|
2851
|
-
'<span class="settings-section-icon"
|
|
2898
|
+
'<span class="settings-section-icon">' + iconSvg("link", { size: 18, strokeWidth: 1.7 }) + '</span>' +
|
|
2852
2899
|
'<div class="settings-section-head-text">' +
|
|
2853
2900
|
'<h4 class="settings-section-heading">App 连接码</h4>' +
|
|
2854
2901
|
'<p class="settings-section-sub">粘贴到 Android App 即可自动连接,无需密码;改密码后失效。</p>' +
|
|
@@ -2876,7 +2923,7 @@
|
|
|
2876
2923
|
'</div>' +
|
|
2877
2924
|
'<div class="settings-notification-section">' +
|
|
2878
2925
|
'<div class="settings-section-head">' +
|
|
2879
|
-
'<span class="settings-section-icon"
|
|
2926
|
+
'<span class="settings-section-icon">' + iconSvg("bell", { size: 18, strokeWidth: 1.7 }) + '</span>' +
|
|
2880
2927
|
'<div class="settings-section-head-text">' +
|
|
2881
2928
|
'<h4 class="settings-section-heading">通知偏好</h4>' +
|
|
2882
2929
|
'<p class="settings-section-sub">提示音与应用内通知气泡</p>' +
|
|
@@ -2910,7 +2957,7 @@
|
|
|
2910
2957
|
'</div>' +
|
|
2911
2958
|
'<div id="native-sound-section" class="settings-notification-section hidden">' +
|
|
2912
2959
|
'<div class="settings-section-head">' +
|
|
2913
|
-
'<span class="settings-section-icon"
|
|
2960
|
+
'<span class="settings-section-icon">' + iconSvg("music", { size: 18, strokeWidth: 1.7 }) + '</span>' +
|
|
2914
2961
|
'<div class="settings-section-head-text">' +
|
|
2915
2962
|
'<h4 class="settings-section-heading">系统通知铃声</h4>' +
|
|
2916
2963
|
'<p class="settings-section-sub">选择 Android 系统通知使用的铃声</p>' +
|
|
@@ -2918,12 +2965,12 @@
|
|
|
2918
2965
|
'</div>' +
|
|
2919
2966
|
'<div class="settings-row-with-action">' +
|
|
2920
2967
|
'<select id="native-sound-select" class="field-input field-select"></select>' +
|
|
2921
|
-
'<button id="native-sound-preview" class="btn btn-secondary btn-sm" type="button"
|
|
2968
|
+
'<button id="native-sound-preview" class="btn btn-secondary btn-sm btn-with-icon" type="button">' + iconSvg("play", { size: 11, strokeWidth: 1.8, fill: "currentColor" }) + '<span>试听</span></button>' +
|
|
2922
2969
|
'</div>' +
|
|
2923
2970
|
'</div>' +
|
|
2924
2971
|
'<div id="native-haptic-section" class="settings-notification-section hidden">' +
|
|
2925
2972
|
'<div class="settings-section-head">' +
|
|
2926
|
-
'<span class="settings-section-icon"
|
|
2973
|
+
'<span class="settings-section-icon">' + iconSvg("vibrate", { size: 18, strokeWidth: 1.7 }) + '</span>' +
|
|
2927
2974
|
'<div class="settings-section-head-text">' +
|
|
2928
2975
|
'<h4 class="settings-section-heading">触感反馈</h4>' +
|
|
2929
2976
|
'<p class="settings-section-sub">按钮操作和任务完成时提供振动反馈</p>' +
|
|
@@ -2941,7 +2988,7 @@
|
|
|
2941
2988
|
'</div>' +
|
|
2942
2989
|
'<div class="settings-notification-section">' +
|
|
2943
2990
|
'<div class="settings-section-head">' +
|
|
2944
|
-
'<span class="settings-section-icon"
|
|
2991
|
+
'<span class="settings-section-icon">' + iconSvg("globe", { size: 18, strokeWidth: 1.7 }) + '</span>' +
|
|
2945
2992
|
'<div class="settings-section-head-text">' +
|
|
2946
2993
|
'<h4 class="settings-section-heading">浏览器通知</h4>' +
|
|
2947
2994
|
'<p class="settings-section-sub">来自系统通知中心的弹窗</p>' +
|
|
@@ -3059,7 +3106,7 @@
|
|
|
3059
3106
|
(typeof WandNative !== "undefined" && typeof WandNative.getAppIcon === "function" ?
|
|
3060
3107
|
'<div class="settings-app-icon-block">' +
|
|
3061
3108
|
'<div class="settings-section-head">' +
|
|
3062
|
-
'<span class="settings-section-icon"
|
|
3109
|
+
'<span class="settings-section-icon">' + iconSvg("palette", { size: 18, strokeWidth: 1.7 }) + '</span>' +
|
|
3063
3110
|
'<div class="settings-section-head-text">' +
|
|
3064
3111
|
'<h4 class="settings-section-heading">应用图标</h4>' +
|
|
3065
3112
|
'<p class="settings-section-sub">选择 App 启动器图标,返回桌面后生效</p>' +
|
|
@@ -3937,8 +3984,7 @@
|
|
|
3937
3984
|
cwdEl.title = cwd;
|
|
3938
3985
|
}
|
|
3939
3986
|
var url = "/api/directory?q=" + encodeURIComponent(cwd) +
|
|
3940
|
-
"&gitStatus=true"
|
|
3941
|
-
(state.fileExplorerShowHidden ? "&showHidden=true" : "");
|
|
3987
|
+
"&gitStatus=true";
|
|
3942
3988
|
fetch(url, { credentials: "same-origin" })
|
|
3943
3989
|
.then(function(res) {
|
|
3944
3990
|
if (!res.ok) throw new Error("Failed to load directory.");
|
|
@@ -4101,8 +4147,7 @@
|
|
|
4101
4147
|
var iconEl2 = item.querySelector(".tree-icon");
|
|
4102
4148
|
if (iconEl2) iconEl2.textContent = "📂";
|
|
4103
4149
|
var url = "/api/directory?q=" + encodeURIComponent(p) +
|
|
4104
|
-
"&gitStatus=true"
|
|
4105
|
-
(state.fileExplorerShowHidden ? "&showHidden=true" : "");
|
|
4150
|
+
"&gitStatus=true";
|
|
4106
4151
|
fetch(url, { credentials: "same-origin" })
|
|
4107
4152
|
.then(function(res) { return res.json(); })
|
|
4108
4153
|
.then(function(payload) {
|
|
@@ -4134,20 +4179,6 @@
|
|
|
4134
4179
|
refreshFileExplorer({ cwd: parent });
|
|
4135
4180
|
}
|
|
4136
4181
|
|
|
4137
|
-
// Toggle the show-hidden flag and persist it.
|
|
4138
|
-
function toggleExplorerHidden() {
|
|
4139
|
-
state.fileExplorerShowHidden = !state.fileExplorerShowHidden;
|
|
4140
|
-
try { localStorage.setItem("wand-file-show-hidden", state.fileExplorerShowHidden ? "1" : "0"); } catch (e) {}
|
|
4141
|
-
var btn = document.getElementById("file-explorer-toggle-hidden");
|
|
4142
|
-
if (btn) {
|
|
4143
|
-
btn.classList.toggle("active", state.fileExplorerShowHidden);
|
|
4144
|
-
btn.setAttribute("aria-pressed", state.fileExplorerShowHidden ? "true" : "false");
|
|
4145
|
-
btn.innerHTML = wandFileIcon(state.fileExplorerShowHidden ? "eye" : "eye-off", { size: 15 });
|
|
4146
|
-
btn.title = state.fileExplorerShowHidden ? "隐藏点开头文件" : "显示隐藏文件";
|
|
4147
|
-
}
|
|
4148
|
-
refreshFileExplorer();
|
|
4149
|
-
}
|
|
4150
|
-
|
|
4151
4182
|
function appendToComposer(text) {
|
|
4152
4183
|
var inputBox = document.getElementById("input-box");
|
|
4153
4184
|
if (!inputBox) return false;
|
|
@@ -4982,9 +5013,9 @@
|
|
|
4982
5013
|
// 新建会话时显示简化的目录选择器(单行紧凑设计)
|
|
4983
5014
|
return '<div class="folder-picker-compact" id="folder-picker-container">' +
|
|
4984
5015
|
'<div class="folder-picker-compact-row">' +
|
|
4985
|
-
'<span class="folder-picker-compact-icon"
|
|
5016
|
+
'<span class="folder-picker-compact-icon">' + iconSvg("folder", { size: 13, strokeWidth: 1.7 }) + '</span>' +
|
|
4986
5017
|
'<input type="text" id="folder-picker-input" class="folder-picker-compact-input" value="' + escapeHtml(currentDir) + '" placeholder="工作目录" autocomplete="off" />' +
|
|
4987
|
-
'<button type="button" id="folder-picker-toggle" class="folder-picker-toggle" title="选择目录"
|
|
5018
|
+
'<button type="button" id="folder-picker-toggle" class="folder-picker-toggle" title="选择目录" aria-label="选择目录">' + iconSvg("chevronDown", { size: 11, strokeWidth: 2 }) + '</button>' +
|
|
4988
5019
|
'</div>' +
|
|
4989
5020
|
'<div id="folder-picker-dropdown" class="folder-picker-dropdown hidden">' +
|
|
4990
5021
|
'<div class="folder-picker-quick-row">' +
|
|
@@ -5014,7 +5045,7 @@
|
|
|
5014
5045
|
}
|
|
5015
5046
|
|
|
5016
5047
|
return '<div class="working-dir-indicator" id="working-dir-indicator" title="' + escapeHtml(displayDir) + '" data-path="' + escapeHtml(displayDir) + '">' +
|
|
5017
|
-
'<span class="working-dir-indicator-icon"
|
|
5048
|
+
'<span class="working-dir-indicator-icon">' + iconSvg("folder", { size: 12, strokeWidth: 1.7 }) + '</span>' +
|
|
5018
5049
|
'<span class="working-dir-indicator-path">' + escapeHtml(displayPath) + '</span>' +
|
|
5019
5050
|
'</div>';
|
|
5020
5051
|
}
|
|
@@ -6337,8 +6368,6 @@
|
|
|
6337
6368
|
if (fileRefresh) fileRefresh.addEventListener("click", function() { refreshFileExplorer(); });
|
|
6338
6369
|
var fileUp = document.getElementById("file-explorer-up");
|
|
6339
6370
|
if (fileUp) fileUp.addEventListener("click", navigateExplorerUp);
|
|
6340
|
-
var fileToggleHidden = document.getElementById("file-explorer-toggle-hidden");
|
|
6341
|
-
if (fileToggleHidden) fileToggleHidden.addEventListener("click", toggleExplorerHidden);
|
|
6342
6371
|
|
|
6343
6372
|
// 路径输入框:支持点击修改路径,回车跳转,Esc 撤销。
|
|
6344
6373
|
var fileCwdInput = document.getElementById("file-explorer-cwd");
|
|
@@ -6787,14 +6816,8 @@
|
|
|
6787
6816
|
setupVisualViewportHandlers();
|
|
6788
6817
|
|
|
6789
6818
|
// 排队条:每次 shell 重渲后,重新挂事件代理 + 刷新内容。
|
|
6790
|
-
// document-level 的 ESC / 外点击 handler 只挂一次(state.__queueBarGlobalAttached 守门)。
|
|
6791
6819
|
attachQueueBarDelegates();
|
|
6792
6820
|
updateQueueBar();
|
|
6793
|
-
if (!state.__queueBarGlobalAttached) {
|
|
6794
|
-
state.__queueBarGlobalAttached = true;
|
|
6795
|
-
document.addEventListener("pointerdown", handleQueueBarOutsideClick, true);
|
|
6796
|
-
document.addEventListener("keydown", handleQueueBarKeydown, true);
|
|
6797
|
-
}
|
|
6798
6821
|
}
|
|
6799
6822
|
|
|
6800
6823
|
function saveWorkingDir(path) {
|
|
@@ -8417,8 +8440,8 @@
|
|
|
8417
8440
|
if (isAutoApproveImpliedByMode(session)) return "";
|
|
8418
8441
|
var enabled = !!session.autoApprovePermissions;
|
|
8419
8442
|
return enabled
|
|
8420
|
-
? '<span id="auto-approve-toggle" class="composer-pill composer-pill-chip auto-approve-indicator active" title="自动批准已启用 — 点击关闭"
|
|
8421
|
-
: '<span id="auto-approve-toggle" class="composer-pill composer-pill-chip auto-approve-indicator" title="自动批准已关闭 — 点击开启"
|
|
8443
|
+
? '<span id="auto-approve-toggle" class="composer-pill composer-pill-chip auto-approve-indicator active" title="自动批准已启用 — 点击关闭">' + iconSvg("shieldCheck", { size: 12, strokeWidth: 1.7, cls: "composer-pill-icon" }) + '<span class="composer-pill-label">自动</span></span>'
|
|
8444
|
+
: '<span id="auto-approve-toggle" class="composer-pill composer-pill-chip auto-approve-indicator" title="自动批准已关闭 — 点击开启">' + iconSvg("shield", { size: 12, strokeWidth: 1.7, cls: "composer-pill-icon" }) + '<span class="composer-pill-label">手动</span></span>';
|
|
8422
8445
|
}
|
|
8423
8446
|
|
|
8424
8447
|
function fetchAvailableModels() {
|
|
@@ -11478,7 +11501,7 @@
|
|
|
11478
11501
|
var a = items[i];
|
|
11479
11502
|
var thumb = a.previewUrl
|
|
11480
11503
|
? '<img src="' + escapeHtml(a.previewUrl) + '" alt="">'
|
|
11481
|
-
: '<span class="att-icon"
|
|
11504
|
+
: '<span class="att-icon">' + iconSvg("file", { size: 13, strokeWidth: 1.7 }) + '</span>';
|
|
11482
11505
|
html += '<span class="attachment-pill" data-index="' + i + '">' +
|
|
11483
11506
|
thumb +
|
|
11484
11507
|
'<span class="att-name" title="' + escapeHtml(a.name) + '">' + escapeHtml(a.name) + '</span>' +
|
|
@@ -12430,104 +12453,79 @@
|
|
|
12430
12453
|
}
|
|
12431
12454
|
|
|
12432
12455
|
// ──────────────────────────────────────────────────────────────────────────
|
|
12433
|
-
//
|
|
12434
|
-
//
|
|
12435
|
-
//
|
|
12436
|
-
//
|
|
12437
|
-
//
|
|
12456
|
+
// 排队气泡条(.queue-bar)—— 垂直堆叠,浮在 "回复中" 状态条上方。
|
|
12457
|
+
// 交互参考 iOS 通讯录右侧的字母选择条:
|
|
12458
|
+
// · 默认只展开队首(即下一个要发的那条),显示编号 + 文本 + × 删除
|
|
12459
|
+
// · 其他消息收起成一根小横杠(指示存在但不占空间)
|
|
12460
|
+
// · 鼠标悬到任意小横杠 → 该条展开、原本展开的那条收回小横杠
|
|
12461
|
+
// · 悬停期间可以按住展开的那条向上 / 向下拖拽 → 换序
|
|
12462
|
+
// 末尾跟一个 ⚡ "立即" 按钮:中断当前回复、把队首作为新输入插队发出去。
|
|
12463
|
+
// 数据源:session.queuedMessages(后端 WS + postStructuredInput 乐观更新)。
|
|
12438
12464
|
// ──────────────────────────────────────────────────────────────────────────
|
|
12439
12465
|
|
|
12440
|
-
var QUEUE_BAR_MAX = 10;
|
|
12466
|
+
var QUEUE_BAR_MAX = 10; // 后端硬上限
|
|
12467
|
+
var QUEUE_CHIP_MAX_TEXT = 24; // 单个气泡展开时显示的字数上限
|
|
12441
12468
|
|
|
12442
|
-
function
|
|
12469
|
+
function queueChipTruncate(text) {
|
|
12443
12470
|
if (typeof text !== "string") return "";
|
|
12444
12471
|
var s = text.replace(/\s+/g, " ").trim();
|
|
12445
|
-
if (s.length <=
|
|
12446
|
-
return s.slice(0,
|
|
12472
|
+
if (s.length <= QUEUE_CHIP_MAX_TEXT) return s;
|
|
12473
|
+
return s.slice(0, QUEUE_CHIP_MAX_TEXT) + "…";
|
|
12447
12474
|
}
|
|
12448
12475
|
|
|
12449
|
-
|
|
12450
|
-
|
|
12451
|
-
|
|
12452
|
-
|
|
12453
|
-
|
|
12454
|
-
if (state.
|
|
12455
|
-
|
|
12456
|
-
|
|
12457
|
-
|
|
12458
|
-
|
|
12459
|
-
|
|
12460
|
-
' aria-expanded="' + (state.queueBarExpanded ? "true" : "false") + '"' +
|
|
12461
|
-
' title="点击查看 / 收起排队消息">' +
|
|
12462
|
-
'<span class="' + dotClass + '" aria-hidden="true"></span>' +
|
|
12463
|
-
'<span class="queue-bar-count">' + (atCapacity ? "队列已满 " : "排队 ") + count + '</span>' +
|
|
12464
|
-
'<span class="queue-bar-sep" aria-hidden="true">·</span>' +
|
|
12465
|
-
'<span class="queue-bar-preview">' + escapeHtml(latestPreview) + '</span>' +
|
|
12466
|
-
'<svg class="queue-bar-chevron" width="11" height="11" viewBox="0 0 24 24"' +
|
|
12467
|
-
' fill="none" stroke="currentColor" stroke-width="2.6" stroke-linecap="round"' +
|
|
12468
|
-
' stroke-linejoin="round" aria-hidden="true"><polyline points="6 15 12 9 18 15"/></svg>' +
|
|
12469
|
-
'</button>' +
|
|
12470
|
-
'<span class="queue-bar-divider" aria-hidden="true"></span>' +
|
|
12471
|
-
'<button type="button" class="queue-bar-promote" data-action="promote"' +
|
|
12472
|
-
' title="中断当前回复,立刻发送队首这条" aria-label="立即发送队首">' +
|
|
12473
|
-
'<svg width="13" height="13" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">' +
|
|
12474
|
-
'<path d="M13 2 L4 14 L11 14 L10 22 L20 9 L13 9 Z"/>' +
|
|
12475
|
-
'</svg>' +
|
|
12476
|
-
'<span class="queue-bar-promote-label">' + escapeHtml(immediateLabel) + '</span>' +
|
|
12477
|
-
'</button>' +
|
|
12478
|
-
'<div class="queue-bar-panel" data-queue-panel="1" role="region" aria-label="排队消息列表">' +
|
|
12479
|
-
'<div class="queue-bar-panel-header">' +
|
|
12480
|
-
'<span class="queue-bar-panel-title">📥 排队中 (' + count + ')</span>' +
|
|
12481
|
-
'<button type="button" class="queue-bar-clear" data-action="clear"' +
|
|
12482
|
-
(count === 0 ? " disabled" : "") + '>清空</button>' +
|
|
12483
|
-
'<button type="button" class="queue-bar-collapse" data-action="collapse" aria-label="收起">' +
|
|
12484
|
-
'收起' +
|
|
12485
|
-
'<svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor"' +
|
|
12486
|
-
' stroke-width="2.6" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">' +
|
|
12487
|
-
'<polyline points="6 9 12 15 18 9"/></svg>' +
|
|
12488
|
-
'</button>' +
|
|
12489
|
-
'</div>' +
|
|
12490
|
-
'<ol class="queue-bar-list" data-queue-list="1"></ol>' +
|
|
12491
|
-
'</div>' +
|
|
12492
|
-
'</div>';
|
|
12493
|
-
return html;
|
|
12476
|
+
// 当前应该展开的下标:拖拽中 → 被拖的那条(data-index 不变);hover → 被 hover 的;否则 → 第 0 项
|
|
12477
|
+
function queueBarExpandedIndex(itemsLength) {
|
|
12478
|
+
if (state.queueBarDrag && typeof state.queueBarDrag.origIndex === "number") {
|
|
12479
|
+
return state.queueBarDrag.origIndex;
|
|
12480
|
+
}
|
|
12481
|
+
if (typeof state.queueBarHoverIndex === "number"
|
|
12482
|
+
&& state.queueBarHoverIndex >= 0
|
|
12483
|
+
&& state.queueBarHoverIndex < itemsLength) {
|
|
12484
|
+
return state.queueBarHoverIndex;
|
|
12485
|
+
}
|
|
12486
|
+
return 0;
|
|
12494
12487
|
}
|
|
12495
12488
|
|
|
12496
|
-
function
|
|
12497
|
-
// ol 内容单独 render —— 拖拽 / 删除 / 展开会频繁动它,外层骨架不重建避免抖动。
|
|
12489
|
+
function renderQueueBarHtml(items, inFlight, atCapacity, immediateLabel) {
|
|
12498
12490
|
var single = items.length <= 1;
|
|
12499
|
-
var
|
|
12491
|
+
var barClass = "queue-bar";
|
|
12492
|
+
if (atCapacity) barClass += " queue-bar-capacity";
|
|
12493
|
+
if (inFlight) barClass += " queue-bar-inflight";
|
|
12494
|
+
var expandedIdx = queueBarExpandedIndex(items.length);
|
|
12495
|
+
var chips = "";
|
|
12500
12496
|
for (var i = 0; i < items.length; i++) {
|
|
12501
12497
|
var raw = items[i] == null ? "" : String(items[i]);
|
|
12502
|
-
var
|
|
12498
|
+
var isExpanded = i === expandedIdx;
|
|
12503
12499
|
var itemClass = "queue-bar-item";
|
|
12504
|
-
if (
|
|
12500
|
+
if (isExpanded) itemClass += " expanded";
|
|
12505
12501
|
if (single) itemClass += " queue-bar-item-single";
|
|
12506
|
-
|
|
12507
|
-
|
|
12508
|
-
|
|
12509
|
-
|
|
12510
|
-
'
|
|
12511
|
-
|
|
12512
|
-
|
|
12513
|
-
'<circle cx="2.2" cy="11.8" r="1.2"/><circle cx="7.8" cy="11.8" r="1.2"/>' +
|
|
12514
|
-
'</svg>' +
|
|
12515
|
-
'</button>' +
|
|
12516
|
-
'<span class="queue-bar-item-index">#' + (i + 1) + '</span>' +
|
|
12517
|
-
'<button type="button" class="queue-bar-item-text" data-action="expand-text"' +
|
|
12518
|
-
' aria-expanded="' + (expanded ? "true" : "false") + '"' +
|
|
12519
|
-
' title="点击展开 / 收起完整内容">' +
|
|
12520
|
-
escapeHtml(raw) +
|
|
12521
|
-
'</button>' +
|
|
12502
|
+
// 拖拽起手区是整个 chip,但 delete 按钮要独占点击。
|
|
12503
|
+
var titleAttr = isExpanded ? raw + "(按住可拖动调整顺序)" : raw;
|
|
12504
|
+
chips +=
|
|
12505
|
+
'<li class="' + itemClass + '" data-index="' + i + '" data-action="drag"' +
|
|
12506
|
+
' title="' + escapeHtml(titleAttr) + '">' +
|
|
12507
|
+
'<span class="queue-bar-item-index" aria-hidden="true">' + (i + 1) + '</span>' +
|
|
12508
|
+
'<span class="queue-bar-item-text">' + escapeHtml(queueChipTruncate(raw)) + '</span>' +
|
|
12522
12509
|
'<button type="button" class="queue-bar-item-delete" data-action="delete"' +
|
|
12523
|
-
|
|
12524
|
-
'<svg width="
|
|
12525
|
-
' stroke-width="
|
|
12510
|
+
' aria-label="删除这条排队消息" title="删除" tabindex="' + (isExpanded ? "0" : "-1") + '">' +
|
|
12511
|
+
'<svg width="9" height="9" viewBox="0 0 24 24" fill="none" stroke="currentColor"' +
|
|
12512
|
+
' stroke-width="3" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">' +
|
|
12526
12513
|
'<line x1="6" y1="6" x2="18" y2="18"/><line x1="6" y1="18" x2="18" y2="6"/></svg>' +
|
|
12527
12514
|
'</button>' +
|
|
12528
12515
|
'</li>';
|
|
12529
12516
|
}
|
|
12530
|
-
|
|
12517
|
+
return (
|
|
12518
|
+
'<div class="' + barClass + '" data-queue-bar="1">' +
|
|
12519
|
+
'<ol class="queue-bar-list" data-queue-list="1">' + chips + '</ol>' +
|
|
12520
|
+
'<button type="button" class="queue-bar-promote" data-action="promote"' +
|
|
12521
|
+
' title="中断当前回复,立刻发送队首这条"' +
|
|
12522
|
+
' aria-label="' + escapeHtml(immediateLabel) + '队首">' +
|
|
12523
|
+
'<svg width="13" height="13" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">' +
|
|
12524
|
+
'<path d="M13 2 L4 14 L11 14 L10 22 L20 9 L13 9 Z"/>' +
|
|
12525
|
+
'</svg>' +
|
|
12526
|
+
'</button>' +
|
|
12527
|
+
'</div>'
|
|
12528
|
+
);
|
|
12531
12529
|
}
|
|
12532
12530
|
|
|
12533
12531
|
function updateQueueBar() {
|
|
@@ -12539,64 +12537,50 @@
|
|
|
12539
12537
|
queue = Array.isArray(queue) ? queue : [];
|
|
12540
12538
|
|
|
12541
12539
|
if (!isStructured || queue.length === 0) {
|
|
12542
|
-
// 队列空 / 非结构化会话:整条隐藏,并清掉展开/逐条展开的本地态。
|
|
12543
12540
|
host.hidden = true;
|
|
12544
12541
|
host.innerHTML = "";
|
|
12545
|
-
state.
|
|
12546
|
-
state.queueBarItemExpanded = {};
|
|
12542
|
+
state.queueBarHoverIndex = null;
|
|
12547
12543
|
return;
|
|
12548
12544
|
}
|
|
12549
12545
|
|
|
12546
|
+
// 拖拽进行中绝不重建 DOM,否则 pointer capture 丢失、气泡闪屏。
|
|
12547
|
+
if (state.queueBarDrag) return;
|
|
12548
|
+
|
|
12550
12549
|
host.hidden = false;
|
|
12551
12550
|
var inFlight = !!(session.structuredState && session.structuredState.inFlight && session.status === "running");
|
|
12552
12551
|
var atCapacity = queue.length >= QUEUE_BAR_MAX;
|
|
12553
|
-
var latest = queueBarTruncatePreview(queue[queue.length - 1]);
|
|
12554
|
-
// inFlight=false 时按钮语义从"插队"退化为"立刻发";文案一并切换让用户不疑惑。
|
|
12555
12552
|
var immediateLabel = inFlight ? "立即" : "发送";
|
|
12556
12553
|
|
|
12557
|
-
|
|
12558
|
-
// 只更新列表内容(且如果数量不变也跳过整段重排)。
|
|
12559
|
-
var existing = host.querySelector(".queue-bar");
|
|
12560
|
-
if (state.queueBarDrag && existing) {
|
|
12561
|
-
var listInDrag = existing.querySelector('[data-queue-list="1"]');
|
|
12562
|
-
if (listInDrag && listInDrag.children.length !== queue.length) {
|
|
12563
|
-
renderQueueBarItems(listInDrag, queue);
|
|
12564
|
-
}
|
|
12565
|
-
return;
|
|
12566
|
-
}
|
|
12567
|
-
|
|
12568
|
-
host.innerHTML = renderQueueBarSkeleton(queue.length, latest, inFlight, atCapacity, immediateLabel);
|
|
12569
|
-
var listEl = host.querySelector('[data-queue-list="1"]');
|
|
12570
|
-
if (listEl) renderQueueBarItems(listEl, queue);
|
|
12554
|
+
host.innerHTML = renderQueueBarHtml(queue, inFlight, atCapacity, immediateLabel);
|
|
12571
12555
|
}
|
|
12572
12556
|
|
|
12573
|
-
//
|
|
12574
|
-
|
|
12575
|
-
|
|
12576
|
-
if (state.queueBarExpanded === next) return;
|
|
12577
|
-
state.queueBarExpanded = next;
|
|
12578
|
-
if (!next) state.queueBarItemExpanded = {};
|
|
12579
|
-
updateQueueBar();
|
|
12580
|
-
}
|
|
12581
|
-
function toggleQueueBar() { setQueueBarExpanded(!state.queueBarExpanded); }
|
|
12582
|
-
|
|
12583
|
-
function handleQueueBarOutsideClick(ev) {
|
|
12584
|
-
if (!state.queueBarExpanded) return;
|
|
12557
|
+
// 只切换 .expanded class,不重建 DOM —— 避免鼠标移过去触发的重建
|
|
12558
|
+
// 让拖拽/输入框焦点等丢失。所有同步状态(hoverIndex / drag)的改变都通过这里反映到 DOM。
|
|
12559
|
+
function reflectQueueBarExpansion() {
|
|
12585
12560
|
var host = document.getElementById("queue-bar-host");
|
|
12586
|
-
if (!host) return;
|
|
12587
|
-
|
|
12588
|
-
|
|
12589
|
-
|
|
12590
|
-
|
|
12591
|
-
|
|
12592
|
-
|
|
12593
|
-
|
|
12594
|
-
|
|
12595
|
-
|
|
12596
|
-
|
|
12561
|
+
if (!host || host.hidden) return;
|
|
12562
|
+
var list = host.querySelector('[data-queue-list="1"]');
|
|
12563
|
+
if (!list) return;
|
|
12564
|
+
var children = list.children;
|
|
12565
|
+
var expandedIdx = queueBarExpandedIndex(children.length);
|
|
12566
|
+
for (var i = 0; i < children.length; i++) {
|
|
12567
|
+
var el = children[i];
|
|
12568
|
+
var should = i === expandedIdx;
|
|
12569
|
+
if (el.classList.contains("expanded") !== should) {
|
|
12570
|
+
el.classList.toggle("expanded", should);
|
|
12571
|
+
var del = el.querySelector('.queue-bar-item-delete');
|
|
12572
|
+
if (del) del.tabIndex = should ? 0 : -1;
|
|
12573
|
+
}
|
|
12597
12574
|
}
|
|
12598
12575
|
}
|
|
12599
12576
|
|
|
12577
|
+
function setQueueBarHoverIndex(idx) {
|
|
12578
|
+
var next = (idx == null ? null : Number(idx));
|
|
12579
|
+
if (state.queueBarHoverIndex === next) return;
|
|
12580
|
+
state.queueBarHoverIndex = next;
|
|
12581
|
+
reflectQueueBarExpansion();
|
|
12582
|
+
}
|
|
12583
|
+
|
|
12600
12584
|
// ── 单条删除 / 全部清空 / 队首插队 ──
|
|
12601
12585
|
function rollbackQueueOptimistic(session, prevQueue) {
|
|
12602
12586
|
updateSessionSnapshot({ id: session.id, queuedMessages: prevQueue });
|
|
@@ -12613,15 +12597,11 @@
|
|
|
12613
12597
|
if (index < 0 || index >= queue.length) return;
|
|
12614
12598
|
var prev = queue.slice();
|
|
12615
12599
|
var next = queue.slice(0, index).concat(queue.slice(index + 1));
|
|
12616
|
-
//
|
|
12617
|
-
|
|
12618
|
-
|
|
12619
|
-
|
|
12620
|
-
|
|
12621
|
-
if (i > index) nextExpanded[i - 1] = state.queueBarItemExpanded[k];
|
|
12622
|
-
else nextExpanded[i] = state.queueBarItemExpanded[k];
|
|
12623
|
-
});
|
|
12624
|
-
state.queueBarItemExpanded = nextExpanded;
|
|
12600
|
+
// hover 下标也要随之收缩,否则删完后展开的是错位的那条
|
|
12601
|
+
if (typeof state.queueBarHoverIndex === "number") {
|
|
12602
|
+
if (state.queueBarHoverIndex === index) state.queueBarHoverIndex = null;
|
|
12603
|
+
else if (state.queueBarHoverIndex > index) state.queueBarHoverIndex -= 1;
|
|
12604
|
+
}
|
|
12625
12605
|
updateSessionSnapshot({ id: session.id, queuedMessages: next });
|
|
12626
12606
|
var refreshed = state.sessions.find(function(s) { return s.id === session.id; }) || session;
|
|
12627
12607
|
state.currentMessages = buildMessagesForRender(refreshed, getPreferredMessages(refreshed, refreshed.output, false));
|
|
@@ -12649,7 +12629,7 @@
|
|
|
12649
12629
|
if (!session) return;
|
|
12650
12630
|
var prev = Array.isArray(session.queuedMessages) ? session.queuedMessages.slice() : [];
|
|
12651
12631
|
if (prev.length === 0) return;
|
|
12652
|
-
state.
|
|
12632
|
+
state.queueBarHoverIndex = null;
|
|
12653
12633
|
updateSessionSnapshot({ id: session.id, queuedMessages: [] });
|
|
12654
12634
|
var refreshed = state.sessions.find(function(s) { return s.id === session.id; }) || session;
|
|
12655
12635
|
state.currentMessages = buildMessagesForRender(refreshed, getPreferredMessages(refreshed, refreshed.output, false));
|
|
@@ -12683,21 +12663,13 @@
|
|
|
12683
12663
|
var prev = queue.slice();
|
|
12684
12664
|
var inFlight = !!(session.structuredState && session.structuredState.inFlight && session.status === "running");
|
|
12685
12665
|
|
|
12686
|
-
//
|
|
12687
|
-
state.
|
|
12688
|
-
|
|
12689
|
-
|
|
12690
|
-
|
|
12691
|
-
if (i === 0) return;
|
|
12692
|
-
out[i - 1] = state.queueBarItemExpanded[k];
|
|
12693
|
-
});
|
|
12694
|
-
return out;
|
|
12695
|
-
})();
|
|
12666
|
+
// 乐观:剥掉队首;hover 下标随之收缩
|
|
12667
|
+
if (typeof state.queueBarHoverIndex === "number") {
|
|
12668
|
+
if (state.queueBarHoverIndex === 0) state.queueBarHoverIndex = null;
|
|
12669
|
+
else state.queueBarHoverIndex -= 1;
|
|
12670
|
+
}
|
|
12696
12671
|
updateSessionSnapshot({ id: session.id, queuedMessages: rest });
|
|
12697
12672
|
|
|
12698
|
-
// 收起面板,让用户视线回到 chat(新消息马上要进 user turn)
|
|
12699
|
-
setQueueBarExpanded(false);
|
|
12700
|
-
|
|
12701
12673
|
var idempotencyKey = (typeof crypto !== "undefined" && crypto.randomUUID)
|
|
12702
12674
|
? crypto.randomUUID()
|
|
12703
12675
|
: (Date.now().toString(36) + "-" + Math.random().toString(36).slice(2, 10));
|
|
@@ -12742,53 +12714,78 @@
|
|
|
12742
12714
|
});
|
|
12743
12715
|
}
|
|
12744
12716
|
|
|
12745
|
-
// ── 拖拽排序(Pointer Events +
|
|
12746
|
-
function queueBarDragStart(ev,
|
|
12717
|
+
// ── 拖拽排序(Pointer Events + 真实高度的 sort/animate)──
|
|
12718
|
+
function queueBarDragStart(ev, chipEl) {
|
|
12747
12719
|
var session = state.sessions.find(function(s) { return s.id === state.selectedId; });
|
|
12748
12720
|
if (!session) return;
|
|
12749
12721
|
var queue = Array.isArray(session.queuedMessages) ? session.queuedMessages.slice() : [];
|
|
12750
12722
|
if (queue.length <= 1) return;
|
|
12751
|
-
|
|
12752
|
-
|
|
12753
|
-
var listEl = itemEl.parentElement;
|
|
12723
|
+
if (!chipEl) return;
|
|
12724
|
+
var listEl = chipEl.parentElement;
|
|
12754
12725
|
if (!listEl) return;
|
|
12755
|
-
var origIndex = Number(
|
|
12726
|
+
var origIndex = Number(chipEl.getAttribute("data-index"));
|
|
12756
12727
|
var siblings = Array.prototype.slice.call(listEl.children);
|
|
12757
12728
|
var rects = siblings.map(function(el) { return el.getBoundingClientRect(); });
|
|
12758
|
-
|
|
12759
|
-
var
|
|
12760
|
-
|
|
12729
|
+
// 真实间距:相邻两个 chip 的 top 差减去前一个高度(容错 hover 状态变化后的高度切换)
|
|
12730
|
+
var gap = 3;
|
|
12731
|
+
if (rects.length >= 2) gap = Math.max(0, rects[1].top - rects[0].top - rects[0].height);
|
|
12761
12732
|
|
|
12762
12733
|
ev.preventDefault();
|
|
12763
|
-
try {
|
|
12734
|
+
try { chipEl.setPointerCapture(ev.pointerId); } catch (_e) {}
|
|
12764
12735
|
if (navigator && navigator.vibrate) { try { navigator.vibrate(8); } catch (_e2) {} }
|
|
12765
12736
|
|
|
12766
12737
|
state.queueBarDrag = {
|
|
12767
12738
|
pointerId: ev.pointerId,
|
|
12768
|
-
handleEl:
|
|
12769
|
-
itemEl:
|
|
12739
|
+
handleEl: chipEl,
|
|
12740
|
+
itemEl: chipEl,
|
|
12770
12741
|
listEl: listEl,
|
|
12771
12742
|
siblings: siblings,
|
|
12772
12743
|
rects: rects,
|
|
12773
12744
|
origIndex: origIndex,
|
|
12774
12745
|
targetIndex: origIndex,
|
|
12775
12746
|
startY: ev.clientY,
|
|
12776
|
-
itemHeight: itemHeight,
|
|
12777
12747
|
gap: gap,
|
|
12778
12748
|
queueSnapshot: queue,
|
|
12779
12749
|
};
|
|
12780
12750
|
|
|
12781
|
-
|
|
12751
|
+
chipEl.classList.add("dragging");
|
|
12752
|
+
// 让被拖元素保持 expanded(即便鼠标已经离开它)
|
|
12753
|
+
reflectQueueBarExpansion();
|
|
12782
12754
|
// 把所有兄弟先标记为"参与平滑动画"
|
|
12783
|
-
siblings.forEach(function(el) { if (el !==
|
|
12755
|
+
siblings.forEach(function(el) { if (el !== chipEl) el.classList.add("queue-bar-item-sliding"); });
|
|
12784
12756
|
|
|
12785
12757
|
var move = function(e) { queueBarDragMove(e); };
|
|
12786
12758
|
var up = function(e) { queueBarDragEnd(e); };
|
|
12787
12759
|
state.queueBarDrag.moveHandler = move;
|
|
12788
12760
|
state.queueBarDrag.upHandler = up;
|
|
12789
|
-
|
|
12790
|
-
|
|
12791
|
-
|
|
12761
|
+
chipEl.addEventListener("pointermove", move);
|
|
12762
|
+
chipEl.addEventListener("pointerup", up);
|
|
12763
|
+
chipEl.addEventListener("pointercancel", up);
|
|
12764
|
+
}
|
|
12765
|
+
|
|
12766
|
+
// 给定 origIndex / target / 真实 rects,算出新排列下每个 sibling 的目标 top。
|
|
12767
|
+
// 用真实高度而不是固定 shift,因为 expanded chip 比 collapsed 高很多。
|
|
12768
|
+
function queueBarComputeNewTops(origIndex, target, rects, gap) {
|
|
12769
|
+
var n = rects.length;
|
|
12770
|
+
var order = [];
|
|
12771
|
+
for (var i = 0; i < n; i++) order.push(i);
|
|
12772
|
+
order.splice(origIndex, 1);
|
|
12773
|
+
order.splice(target, 0, origIndex);
|
|
12774
|
+
var top = rects[0].top;
|
|
12775
|
+
// list 是右对齐 column flex,所有元素相对 list 左边对齐 — 我们只关心 top
|
|
12776
|
+
// 用第一个 rect 的 top 作为锚点累加。
|
|
12777
|
+
// 但 list 起始位置不一定是 rects[0].top(rects[0] 现在变到 order[0] 的位置)
|
|
12778
|
+
// 这里需要找原本的 list top —— 取 rects 里最小 top 即可。
|
|
12779
|
+
var listTop = rects[0].top;
|
|
12780
|
+
for (var k = 1; k < n; k++) if (rects[k].top < listTop) listTop = rects[k].top;
|
|
12781
|
+
var newTops = {};
|
|
12782
|
+
var cursor = listTop;
|
|
12783
|
+
for (var newPos = 0; newPos < n; newPos++) {
|
|
12784
|
+
var oldIdx = order[newPos];
|
|
12785
|
+
newTops[oldIdx] = cursor;
|
|
12786
|
+
cursor += rects[oldIdx].height + gap;
|
|
12787
|
+
}
|
|
12788
|
+
return newTops;
|
|
12792
12789
|
}
|
|
12793
12790
|
|
|
12794
12791
|
function queueBarDragMove(ev) {
|
|
@@ -12809,13 +12806,11 @@
|
|
|
12809
12806
|
}
|
|
12810
12807
|
if (target !== d.targetIndex) {
|
|
12811
12808
|
d.targetIndex = target;
|
|
12812
|
-
//
|
|
12813
|
-
var
|
|
12809
|
+
// 按真实高度精确算每个 sibling 的新 top
|
|
12810
|
+
var newTops = queueBarComputeNewTops(d.origIndex, target, d.rects, d.gap);
|
|
12814
12811
|
d.siblings.forEach(function(el, idx) {
|
|
12815
12812
|
if (idx === d.origIndex) return;
|
|
12816
|
-
var move =
|
|
12817
|
-
if (d.origIndex < target && idx > d.origIndex && idx <= target) move = -shift;
|
|
12818
|
-
else if (d.origIndex > target && idx < d.origIndex && idx >= target) move = shift;
|
|
12813
|
+
var move = newTops[idx] - d.rects[idx].top;
|
|
12819
12814
|
el.style.transform = move ? "translateY(" + move + "px)" : "";
|
|
12820
12815
|
});
|
|
12821
12816
|
}
|
|
@@ -12855,14 +12850,8 @@
|
|
|
12855
12850
|
order.splice(targetIndex, 0, origIndex);
|
|
12856
12851
|
var nextQueue = order.map(function(i) { return queueSnapshot[i]; });
|
|
12857
12852
|
|
|
12858
|
-
//
|
|
12859
|
-
|
|
12860
|
-
Object.keys(state.queueBarItemExpanded).forEach(function(k) {
|
|
12861
|
-
var oldI = Number(k);
|
|
12862
|
-
var newI = order.indexOf(oldI);
|
|
12863
|
-
if (newI >= 0) nextExpanded[newI] = state.queueBarItemExpanded[k];
|
|
12864
|
-
});
|
|
12865
|
-
state.queueBarItemExpanded = nextExpanded;
|
|
12853
|
+
// hover 下标迁移到新位置(拖拽放手时鼠标停在 targetIndex 上)
|
|
12854
|
+
state.queueBarHoverIndex = targetIndex;
|
|
12866
12855
|
|
|
12867
12856
|
var session = state.sessions.find(function(s) { return s.id === state.selectedId; });
|
|
12868
12857
|
if (!session) { updateQueueBar(); return; }
|
|
@@ -12897,33 +12886,36 @@
|
|
|
12897
12886
|
var actionEl = ev.target && ev.target.closest ? ev.target.closest("[data-action]") : null;
|
|
12898
12887
|
if (!actionEl || !host.contains(actionEl)) return;
|
|
12899
12888
|
var action = actionEl.getAttribute("data-action");
|
|
12900
|
-
if (action === "drag") return; // 拖拽由 pointerdown
|
|
12889
|
+
if (action === "drag") return; // 拖拽由 pointerdown 处理,吞掉 click
|
|
12901
12890
|
ev.preventDefault();
|
|
12902
12891
|
ev.stopPropagation();
|
|
12903
|
-
if (action === "toggle") { toggleQueueBar(); return; }
|
|
12904
|
-
if (action === "collapse") { setQueueBarExpanded(false); return; }
|
|
12905
12892
|
if (action === "promote") { queueBarPromoteHead(); return; }
|
|
12906
|
-
if (action === "clear") { queueBarClearAll(); return; }
|
|
12907
12893
|
if (action === "delete") {
|
|
12908
12894
|
var itemEl = actionEl.closest(".queue-bar-item");
|
|
12909
12895
|
if (itemEl) queueBarDeleteItem(Number(itemEl.getAttribute("data-index")));
|
|
12910
12896
|
return;
|
|
12911
12897
|
}
|
|
12912
|
-
if (action === "expand-text") {
|
|
12913
|
-
var item = actionEl.closest(".queue-bar-item");
|
|
12914
|
-
if (!item) return;
|
|
12915
|
-
var idx = Number(item.getAttribute("data-index"));
|
|
12916
|
-
state.queueBarItemExpanded[idx] = !state.queueBarItemExpanded[idx];
|
|
12917
|
-
item.classList.toggle("expanded", !!state.queueBarItemExpanded[idx]);
|
|
12918
|
-
actionEl.setAttribute("aria-expanded", state.queueBarItemExpanded[idx] ? "true" : "false");
|
|
12919
|
-
return;
|
|
12920
|
-
}
|
|
12921
12898
|
});
|
|
12899
|
+
// hover 跟随:鼠标移到哪一条,哪一条就展开(拖拽进行中不响应,免得抢拖拽)
|
|
12900
|
+
host.addEventListener("mouseover", function(ev) {
|
|
12901
|
+
if (state.queueBarDrag) return;
|
|
12902
|
+
var chip = ev.target && ev.target.closest ? ev.target.closest(".queue-bar-item") : null;
|
|
12903
|
+
if (!chip || !host.contains(chip)) return;
|
|
12904
|
+
setQueueBarHoverIndex(Number(chip.getAttribute("data-index")));
|
|
12905
|
+
});
|
|
12906
|
+
host.addEventListener("mouseleave", function() {
|
|
12907
|
+
if (state.queueBarDrag) return;
|
|
12908
|
+
setQueueBarHoverIndex(null);
|
|
12909
|
+
});
|
|
12910
|
+
// 整个气泡都是拖拽起手区。delete / promote 按钮通过 closest 检查跳过
|
|
12922
12911
|
host.addEventListener("pointerdown", function(ev) {
|
|
12923
12912
|
if (ev.button !== undefined && ev.button !== 0) return;
|
|
12924
|
-
|
|
12925
|
-
|
|
12926
|
-
|
|
12913
|
+
if (ev.target && ev.target.closest && ev.target.closest('[data-action="delete"], [data-action="promote"]')) return;
|
|
12914
|
+
var chip = ev.target && ev.target.closest ? ev.target.closest('.queue-bar-item') : null;
|
|
12915
|
+
if (!chip) return;
|
|
12916
|
+
// 拖拽前先把这条切到 expanded(鼠标按下时通常已经 hovered,但触屏没 hover)
|
|
12917
|
+
setQueueBarHoverIndex(Number(chip.getAttribute("data-index")));
|
|
12918
|
+
queueBarDragStart(ev, chip);
|
|
12927
12919
|
});
|
|
12928
12920
|
}
|
|
12929
12921
|
|
|
@@ -16173,10 +16165,10 @@
|
|
|
16173
16165
|
'</span>' +
|
|
16174
16166
|
'<span class="approval-stats-popup" id="approval-stats-popup">' +
|
|
16175
16167
|
'<span class="approval-stats-popup-title">自动批准统计</span>' +
|
|
16176
|
-
(stats.command > 0 ? '<span class="approval-stats-row"><span class="approval-stats-row-icon"
|
|
16177
|
-
(stats.file > 0 ? '<span class="approval-stats-row"><span class="approval-stats-row-icon"
|
|
16178
|
-
(stats.tool > 0 ? '<span class="approval-stats-row"><span class="approval-stats-row-icon"
|
|
16179
|
-
'<span class="approval-stats-row approval-stats-row-total"><span class="approval-stats-row-icon"
|
|
16168
|
+
(stats.command > 0 ? '<span class="approval-stats-row"><span class="approval-stats-row-icon">' + iconSvg("terminal", { size: 12, strokeWidth: 1.8 }) + '</span><span class="approval-stats-row-label">命令执行</span><span class="approval-stats-row-count">' + stats.command + '</span></span>' : '') +
|
|
16169
|
+
(stats.file > 0 ? '<span class="approval-stats-row"><span class="approval-stats-row-icon">' + iconSvg("edit", { size: 12, strokeWidth: 1.8 }) + '</span><span class="approval-stats-row-label">文件写入</span><span class="approval-stats-row-count">' + stats.file + '</span></span>' : '') +
|
|
16170
|
+
(stats.tool > 0 ? '<span class="approval-stats-row"><span class="approval-stats-row-icon">' + iconSvg("wrench", { size: 12, strokeWidth: 1.8 }) + '</span><span class="approval-stats-row-label">其他工具</span><span class="approval-stats-row-count">' + stats.tool + '</span></span>' : '') +
|
|
16171
|
+
'<span class="approval-stats-row approval-stats-row-total"><span class="approval-stats-row-icon">' + iconSvg("sigma", { size: 12, strokeWidth: 1.8 }) + '</span><span class="approval-stats-row-label">合计</span><span class="approval-stats-row-count">' + stats.total + '</span></span>' +
|
|
16180
16172
|
'</span>';
|
|
16181
16173
|
// Pulse animation on the badge
|
|
16182
16174
|
var badge = container.querySelector(".approval-stats-badge");
|
|
@@ -16292,11 +16284,11 @@
|
|
|
16292
16284
|
if (enabled) {
|
|
16293
16285
|
toggle.className = base + " active";
|
|
16294
16286
|
toggle.title = "自动批准已启用 — 点击关闭";
|
|
16295
|
-
toggle.
|
|
16287
|
+
toggle.innerHTML = iconSvg("shieldCheck", { size: 12, strokeWidth: 1.7, cls: "composer-pill-icon" }) + '<span class="composer-pill-label">自动</span>';
|
|
16296
16288
|
} else {
|
|
16297
16289
|
toggle.className = base;
|
|
16298
16290
|
toggle.title = "自动批准已关闭 — 点击开启";
|
|
16299
|
-
toggle.
|
|
16291
|
+
toggle.innerHTML = iconSvg("shield", { size: 12, strokeWidth: 1.7, cls: "composer-pill-icon" }) + '<span class="composer-pill-label">手动</span>';
|
|
16300
16292
|
}
|
|
16301
16293
|
}
|
|
16302
16294
|
|