claude-opencode-viewer 2.6.37 → 2.6.39
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/index-pc.html +290 -48
- package/index.html +287 -84
- package/package.json +1 -1
- package/server.js +128 -86
package/index-pc.html
CHANGED
|
@@ -761,6 +761,58 @@
|
|
|
761
761
|
padding: 1px 6px;
|
|
762
762
|
border-radius: 8px;
|
|
763
763
|
}
|
|
764
|
+
/* 启动对话框 */
|
|
765
|
+
#startup-overlay {
|
|
766
|
+
display: none;
|
|
767
|
+
position: fixed; top: 0; left: 0; right: 0; bottom: 0;
|
|
768
|
+
background: rgba(0, 0, 0, 0.75);
|
|
769
|
+
z-index: 99999;
|
|
770
|
+
align-items: center; justify-content: center;
|
|
771
|
+
}
|
|
772
|
+
#startup-overlay.visible { display: flex; }
|
|
773
|
+
#startup-card {
|
|
774
|
+
background: #1e1e2e; border-radius: 12px; padding: 28px;
|
|
775
|
+
max-width: 500px; width: 90%; color: #ccc;
|
|
776
|
+
box-shadow: 0 8px 32px rgba(0,0,0,0.5);
|
|
777
|
+
}
|
|
778
|
+
#startup-card h3 {
|
|
779
|
+
margin: 0 0 16px 0; color: #e0e0e0; font-size: 16px; font-weight: 600;
|
|
780
|
+
}
|
|
781
|
+
.startup-session-info {
|
|
782
|
+
background: #252535; border-radius: 8px; padding: 14px; margin-bottom: 16px;
|
|
783
|
+
border-left: 3px solid #4a9eff;
|
|
784
|
+
}
|
|
785
|
+
.startup-session-info.claude-session { border-left-color: #4a9eff; }
|
|
786
|
+
.startup-session-mode {
|
|
787
|
+
font-size: 12px; font-weight: 600; margin-bottom: 6px; color: #aaa;
|
|
788
|
+
}
|
|
789
|
+
.startup-session-mode .mode-tag {
|
|
790
|
+
display: inline-block; padding: 2px 8px; border-radius: 4px; font-size: 11px;
|
|
791
|
+
}
|
|
792
|
+
.startup-session-mode .mode-tag.opencode { background: #1a3a2a; color: #4ec9b0; }
|
|
793
|
+
.startup-session-mode .mode-tag.claude { background: #1a2a3a; color: #4a9eff; }
|
|
794
|
+
.startup-session-preview {
|
|
795
|
+
font-size: 13px; color: #ddd; margin-bottom: 6px;
|
|
796
|
+
overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
|
|
797
|
+
}
|
|
798
|
+
.startup-session-meta {
|
|
799
|
+
font-size: 11px; color: #888;
|
|
800
|
+
}
|
|
801
|
+
.startup-buttons {
|
|
802
|
+
display: flex; gap: 10px; margin-top: 8px;
|
|
803
|
+
}
|
|
804
|
+
.startup-buttons button {
|
|
805
|
+
flex: 1; padding: 10px 16px; border: none; border-radius: 8px;
|
|
806
|
+
font-size: 14px; cursor: pointer; font-weight: 500; transition: background 0.2s;
|
|
807
|
+
}
|
|
808
|
+
.startup-btn-restore {
|
|
809
|
+
background: #4a9eff; color: #fff;
|
|
810
|
+
}
|
|
811
|
+
.startup-btn-restore:hover { background: #3a8eef; }
|
|
812
|
+
.startup-btn-new {
|
|
813
|
+
background: #333; color: #ccc;
|
|
814
|
+
}
|
|
815
|
+
.startup-btn-new:hover { background: #444; }
|
|
764
816
|
</style>
|
|
765
817
|
</head>
|
|
766
818
|
<body>
|
|
@@ -955,6 +1007,7 @@
|
|
|
955
1007
|
|
|
956
1008
|
<div id="copy-toast">已复制</div>
|
|
957
1009
|
<script src="https://cdn.jsdelivr.net/npm/@xterm/xterm@5.5.0/lib/xterm.min.js"></script>
|
|
1010
|
+
<script src="https://cdn.jsdelivr.net/npm/@xterm/addon-webgl@0.18.0/lib/addon-webgl.min.js"></script>
|
|
958
1011
|
<script>
|
|
959
1012
|
(function() {
|
|
960
1013
|
var isMobile = /Mobi|Android|iPhone|iPad|iPod/i.test(navigator.userAgent);
|
|
@@ -964,6 +1017,7 @@
|
|
|
964
1017
|
var currentMode = 'claude';
|
|
965
1018
|
var isTransitioning = false;
|
|
966
1019
|
var isBufferReplay = true; // 初始缓冲区回放中,不弹 toast
|
|
1020
|
+
var startupDialogShown = false;
|
|
967
1021
|
|
|
968
1022
|
var term = new Terminal({
|
|
969
1023
|
cursorBlink: !isMobile,
|
|
@@ -983,6 +1037,17 @@
|
|
|
983
1037
|
|
|
984
1038
|
term.open(document.getElementById('terminal'));
|
|
985
1039
|
|
|
1040
|
+
// WebGL 渲染器:GPU 加速绘制
|
|
1041
|
+
if (window.WebglAddon) {
|
|
1042
|
+
try {
|
|
1043
|
+
var webglAddon = new WebglAddon.WebglAddon();
|
|
1044
|
+
webglAddon.onContextLoss(function() {
|
|
1045
|
+
webglAddon.dispose();
|
|
1046
|
+
});
|
|
1047
|
+
term.loadAddon(webglAddon);
|
|
1048
|
+
} catch(e) {}
|
|
1049
|
+
}
|
|
1050
|
+
|
|
986
1051
|
// PC端复制:用 xterm.js selection API 获取纯文本,避免复制出乱码
|
|
987
1052
|
document.getElementById('terminal').addEventListener('copy', function(e) {
|
|
988
1053
|
var sel = term.getSelection();
|
|
@@ -1432,6 +1497,92 @@
|
|
|
1432
1497
|
}, 50);
|
|
1433
1498
|
});
|
|
1434
1499
|
|
|
1500
|
+
// === 启动对话框 ===
|
|
1501
|
+
function showStartupDialog() {
|
|
1502
|
+
fetch(basePath + '/api/last-sessions')
|
|
1503
|
+
.then(function(r) { return r.json(); })
|
|
1504
|
+
.then(function(data) {
|
|
1505
|
+
var oc = data.opencode;
|
|
1506
|
+
var cl = data.claude;
|
|
1507
|
+
// 无任何历史 → 直接新建 claude
|
|
1508
|
+
if (!oc && !cl) {
|
|
1509
|
+
sendInit('claude', null);
|
|
1510
|
+
return;
|
|
1511
|
+
}
|
|
1512
|
+
// 确定最近的会话
|
|
1513
|
+
var latest = null, latestMode = 'claude';
|
|
1514
|
+
if (oc && cl) {
|
|
1515
|
+
if (oc.mtime >= cl.mtime) { latest = oc; latestMode = 'opencode'; }
|
|
1516
|
+
else { latest = cl; latestMode = 'claude'; }
|
|
1517
|
+
} else if (oc) { latest = oc; latestMode = 'opencode'; }
|
|
1518
|
+
else { latest = cl; latestMode = 'claude'; }
|
|
1519
|
+
|
|
1520
|
+
// 渲染会话信息
|
|
1521
|
+
var infoDiv = document.getElementById('startup-session-info');
|
|
1522
|
+
var modeLabel = latestMode === 'claude' ? 'Claude Code' : 'OpenCode';
|
|
1523
|
+
var modeClass = latestMode === 'claude' ? 'claude' : 'opencode';
|
|
1524
|
+
var timeAgo = getTimeAgo(latest.mtime);
|
|
1525
|
+
var dir = latest.directory || '';
|
|
1526
|
+
if (dir.startsWith('/Users/')) dir = '~' + dir.substring(dir.indexOf('/', 1));
|
|
1527
|
+
var preview = latest.preview || '(无预览)';
|
|
1528
|
+
if (preview.length > 100) preview = preview.substring(0, 100) + '...';
|
|
1529
|
+
|
|
1530
|
+
infoDiv.innerHTML =
|
|
1531
|
+
'<div class="startup-session-info ' + (latestMode === 'claude' ? 'claude-session' : '') + '">' +
|
|
1532
|
+
'<div class="startup-session-mode"><span class="mode-tag ' + modeClass + '">' + modeLabel + '</span> · ' + timeAgo + '</div>' +
|
|
1533
|
+
'<div class="startup-session-preview">' + escapeHtml(preview) + '</div>' +
|
|
1534
|
+
(dir ? '<div class="startup-session-meta">' + escapeHtml(dir) + '</div>' : '') +
|
|
1535
|
+
'</div>';
|
|
1536
|
+
|
|
1537
|
+
// 恢复按钮样式
|
|
1538
|
+
var restoreBtn = document.getElementById('startup-btn-restore');
|
|
1539
|
+
restoreBtn.className = 'startup-btn-restore';
|
|
1540
|
+
restoreBtn.textContent = '恢复会话';
|
|
1541
|
+
|
|
1542
|
+
// 绑定事件
|
|
1543
|
+
restoreBtn.onclick = function() {
|
|
1544
|
+
hideStartupDialog();
|
|
1545
|
+
sendInit(latestMode, latest.id);
|
|
1546
|
+
};
|
|
1547
|
+
document.getElementById('startup-btn-new').onclick = function() {
|
|
1548
|
+
hideStartupDialog();
|
|
1549
|
+
sendInit('claude', null);
|
|
1550
|
+
};
|
|
1551
|
+
|
|
1552
|
+
// 显示对话框
|
|
1553
|
+
document.getElementById('startup-overlay').classList.add('visible');
|
|
1554
|
+
})
|
|
1555
|
+
.catch(function() {
|
|
1556
|
+
// API 失败,直接新建
|
|
1557
|
+
sendInit('claude', null);
|
|
1558
|
+
});
|
|
1559
|
+
}
|
|
1560
|
+
|
|
1561
|
+
function hideStartupDialog() {
|
|
1562
|
+
document.getElementById('startup-overlay').classList.remove('visible');
|
|
1563
|
+
}
|
|
1564
|
+
|
|
1565
|
+
function sendInit(mode, sessionId) {
|
|
1566
|
+
if (!ws || ws.readyState !== 1) return;
|
|
1567
|
+
currentMode = mode;
|
|
1568
|
+
modeSelect.value = mode;
|
|
1569
|
+
document.getElementById('mode-label').textContent = '';
|
|
1570
|
+
var msg = { type: 'init', mode: mode };
|
|
1571
|
+
if (sessionId) msg.sessionId = sessionId;
|
|
1572
|
+
ws.send(JSON.stringify(msg));
|
|
1573
|
+
}
|
|
1574
|
+
|
|
1575
|
+
function getTimeAgo(ts) {
|
|
1576
|
+
var diff = Date.now() - ts;
|
|
1577
|
+
var min = Math.floor(diff / 60000);
|
|
1578
|
+
if (min < 1) return '刚刚';
|
|
1579
|
+
if (min < 60) return min + ' 分钟前';
|
|
1580
|
+
var hr = Math.floor(min / 60);
|
|
1581
|
+
if (hr < 24) return hr + ' 小时前';
|
|
1582
|
+
var day = Math.floor(hr / 24);
|
|
1583
|
+
return day + ' 天前';
|
|
1584
|
+
}
|
|
1585
|
+
|
|
1435
1586
|
function connect() {
|
|
1436
1587
|
var proto = location.protocol === 'https:' ? 'wss:' : 'ws:';
|
|
1437
1588
|
ws = new WebSocket(proto + '//' + location.host + basePath + '/ws');
|
|
@@ -1441,11 +1592,13 @@
|
|
|
1441
1592
|
resize();
|
|
1442
1593
|
rebindTouchScroll();
|
|
1443
1594
|
setTimeout(function() { isBufferReplay = false; }, 500);
|
|
1595
|
+
// 不在这里初始化,等 state 消息判断是否需要弹对话框
|
|
1444
1596
|
};
|
|
1445
1597
|
|
|
1446
1598
|
ws.onclose = function() {
|
|
1447
1599
|
ws = null;
|
|
1448
|
-
term.reset();
|
|
1600
|
+
term.reset();
|
|
1601
|
+
term.write('\r\n \x1b[33m连接断开,正在重连...\x1b[0m\r\n');
|
|
1449
1602
|
setTimeout(connect, 2000);
|
|
1450
1603
|
};
|
|
1451
1604
|
|
|
@@ -1453,7 +1606,7 @@
|
|
|
1453
1606
|
try {
|
|
1454
1607
|
var msg = JSON.parse(e.data);
|
|
1455
1608
|
if (msg.type === 'data') {
|
|
1456
|
-
if (!isCreatingNewSession) {
|
|
1609
|
+
if (!isCreatingNewSession && !isTransitioning) {
|
|
1457
1610
|
throttledWrite(msg.data);
|
|
1458
1611
|
}
|
|
1459
1612
|
}
|
|
@@ -1464,36 +1617,55 @@
|
|
|
1464
1617
|
}
|
|
1465
1618
|
}
|
|
1466
1619
|
else if (msg.type === 'mode') {
|
|
1620
|
+
if (writeTimer) { cancelAnimationFrame(writeTimer); writeTimer = null; }
|
|
1621
|
+
writeBuffer = '';
|
|
1622
|
+
term.reset();
|
|
1467
1623
|
endTransition(msg.mode);
|
|
1468
|
-
|
|
1624
|
+
if (msg.buffer) {
|
|
1625
|
+
term.write(msg.buffer);
|
|
1626
|
+
}
|
|
1469
1627
|
rebindTouchScroll();
|
|
1470
1628
|
}
|
|
1471
1629
|
else if (msg.type === 'switching') {
|
|
1472
|
-
|
|
1473
|
-
term.reset();
|
|
1630
|
+
if (writeTimer) { cancelAnimationFrame(writeTimer); writeTimer = null; }
|
|
1474
1631
|
writeBuffer = '';
|
|
1632
|
+
term.clear();
|
|
1475
1633
|
}
|
|
1476
1634
|
else if (msg.type === 'state') {
|
|
1477
1635
|
if (msg.mode) {
|
|
1478
1636
|
currentMode = msg.mode;
|
|
1479
1637
|
modeSelect.value = msg.mode;
|
|
1480
|
-
|
|
1638
|
+
document.getElementById('mode-label').textContent = '';
|
|
1639
|
+
}
|
|
1640
|
+
// 服务端无运行进程,显示启动对话框
|
|
1641
|
+
if (!msg.running && !startupDialogShown) {
|
|
1642
|
+
startupDialogShown = true;
|
|
1643
|
+
showStartupDialog();
|
|
1481
1644
|
}
|
|
1482
1645
|
}
|
|
1483
1646
|
else if (msg.type === 'restored') {
|
|
1484
|
-
|
|
1647
|
+
if (writeTimer) { cancelAnimationFrame(writeTimer); writeTimer = null; }
|
|
1648
|
+
writeBuffer = '';
|
|
1485
1649
|
term.reset();
|
|
1650
|
+
if (msg.buffer) {
|
|
1651
|
+
term.write(msg.buffer);
|
|
1652
|
+
}
|
|
1486
1653
|
}
|
|
1487
1654
|
else if (msg.type === 'restore-error') {
|
|
1488
|
-
// 恢复失败
|
|
1489
1655
|
term.write('恢复失败: ' + msg.error + '\r\n');
|
|
1490
1656
|
}
|
|
1491
1657
|
else if (msg.type === 'started') {
|
|
1492
1658
|
rebindTouchScroll();
|
|
1659
|
+
preloadData();
|
|
1493
1660
|
}
|
|
1494
1661
|
else if (msg.type === 'new-session-ok') {
|
|
1495
|
-
|
|
1662
|
+
if (writeTimer) { cancelAnimationFrame(writeTimer); writeTimer = null; }
|
|
1663
|
+
writeBuffer = '';
|
|
1496
1664
|
term.reset();
|
|
1665
|
+
if (msg.buffer) {
|
|
1666
|
+
term.write(msg.buffer);
|
|
1667
|
+
}
|
|
1668
|
+
isCreatingNewSession = false;
|
|
1497
1669
|
}
|
|
1498
1670
|
else if (msg.type === 'new-session-error') {
|
|
1499
1671
|
isCreatingNewSession = false;
|
|
@@ -1519,11 +1691,14 @@
|
|
|
1519
1691
|
});
|
|
1520
1692
|
}
|
|
1521
1693
|
|
|
1522
|
-
//
|
|
1694
|
+
// 页面卸载前保存输入缓存,并通知服务端退出
|
|
1523
1695
|
window.addEventListener('beforeunload', function() {
|
|
1524
1696
|
if (currentInputBuffer) {
|
|
1525
1697
|
saveInputCache();
|
|
1526
1698
|
}
|
|
1699
|
+
if (ws && ws.readyState === WebSocket.OPEN) {
|
|
1700
|
+
ws.send(JSON.stringify({ type: 'quit' }));
|
|
1701
|
+
}
|
|
1527
1702
|
});
|
|
1528
1703
|
|
|
1529
1704
|
// 页面可见性变化时保存缓存
|
|
@@ -2038,6 +2213,29 @@
|
|
|
2038
2213
|
var diffChanges = [];
|
|
2039
2214
|
var diffSelectedFile = null;
|
|
2040
2215
|
|
|
2216
|
+
// 预加载缓存
|
|
2217
|
+
var cachedGitStatus = null;
|
|
2218
|
+
var cachedDocs = null;
|
|
2219
|
+
var gitStatusLoading = false;
|
|
2220
|
+
var docsLoading = false;
|
|
2221
|
+
|
|
2222
|
+
function preloadData() {
|
|
2223
|
+
if (!gitStatusLoading) {
|
|
2224
|
+
gitStatusLoading = true;
|
|
2225
|
+
fetch(basePath + '/api/git-status')
|
|
2226
|
+
.then(function(r) { return r.json(); })
|
|
2227
|
+
.then(function(data) { cachedGitStatus = data; gitStatusLoading = false; })
|
|
2228
|
+
.catch(function() { gitStatusLoading = false; });
|
|
2229
|
+
}
|
|
2230
|
+
if (!docsLoading) {
|
|
2231
|
+
docsLoading = true;
|
|
2232
|
+
fetch(basePath + '/api/docs')
|
|
2233
|
+
.then(function(r) { return r.json(); })
|
|
2234
|
+
.then(function(data) { cachedDocs = data; docsLoading = false; })
|
|
2235
|
+
.catch(function() { docsLoading = false; });
|
|
2236
|
+
}
|
|
2237
|
+
}
|
|
2238
|
+
|
|
2041
2239
|
var STATUS_COLORS = {
|
|
2042
2240
|
'M': '#e2c08d', 'A': '#73c991', 'D': '#f14c4c',
|
|
2043
2241
|
'R': '#73c991', 'C': '#73c991', 'U': '#e2c08d',
|
|
@@ -2049,27 +2247,42 @@
|
|
|
2049
2247
|
var bar = document.getElementById('git-diff-bar');
|
|
2050
2248
|
if (diffBarVisible) {
|
|
2051
2249
|
bar.classList.add('visible');
|
|
2052
|
-
loadGitStatus();
|
|
2250
|
+
loadGitStatus(false);
|
|
2053
2251
|
} else {
|
|
2054
2252
|
bar.classList.remove('visible');
|
|
2055
2253
|
diffSelectedFile = null;
|
|
2056
2254
|
}
|
|
2057
2255
|
}
|
|
2058
2256
|
|
|
2059
|
-
function loadGitStatus() {
|
|
2257
|
+
function loadGitStatus(forceRefresh) {
|
|
2060
2258
|
var fileList = document.getElementById('git-diff-file-list');
|
|
2061
|
-
|
|
2062
|
-
|
|
2259
|
+
|
|
2260
|
+
if (cachedGitStatus && !forceRefresh) {
|
|
2261
|
+
diffChanges = cachedGitStatus.changes || [];
|
|
2262
|
+
document.getElementById('git-diff-count').textContent = diffChanges.length;
|
|
2263
|
+
renderDiffFileList();
|
|
2264
|
+
return;
|
|
2265
|
+
}
|
|
2266
|
+
|
|
2267
|
+
fileList.innerHTML = '<div class="git-diff-loading">' + (forceRefresh ? '正在刷新...' : '正在查询 git status...') + '</div>';
|
|
2268
|
+
document.getElementById('git-diff-count').textContent = '...';
|
|
2269
|
+
|
|
2270
|
+
if (gitStatusLoading && !forceRefresh) return;
|
|
2271
|
+
gitStatusLoading = true;
|
|
2272
|
+
if (forceRefresh) cachedGitStatus = null;
|
|
2063
2273
|
|
|
2064
2274
|
fetch(basePath + '/api/git-status')
|
|
2065
2275
|
.then(function(r) { return r.json(); })
|
|
2066
2276
|
.then(function(data) {
|
|
2277
|
+
cachedGitStatus = data;
|
|
2278
|
+
gitStatusLoading = false;
|
|
2067
2279
|
diffChanges = data.changes || [];
|
|
2068
2280
|
document.getElementById('git-diff-count').textContent = diffChanges.length;
|
|
2069
|
-
renderDiffFileList();
|
|
2281
|
+
if (diffBarVisible) renderDiffFileList();
|
|
2070
2282
|
})
|
|
2071
2283
|
.catch(function() {
|
|
2072
|
-
|
|
2284
|
+
gitStatusLoading = false;
|
|
2285
|
+
if (diffBarVisible) fileList.innerHTML = '<div class="git-diff-error">无法加载 git status</div>';
|
|
2073
2286
|
});
|
|
2074
2287
|
}
|
|
2075
2288
|
|
|
@@ -2198,7 +2411,7 @@
|
|
|
2198
2411
|
document.getElementById('close-diff').addEventListener('click', toggleDiffBar);
|
|
2199
2412
|
document.getElementById('refresh-diff').addEventListener('click', function(e) {
|
|
2200
2413
|
e.stopPropagation();
|
|
2201
|
-
loadGitStatus();
|
|
2414
|
+
loadGitStatus(true);
|
|
2202
2415
|
// 重置 diff 内容区
|
|
2203
2416
|
diffSelectedFile = null;
|
|
2204
2417
|
document.getElementById('git-diff-content-area').innerHTML =
|
|
@@ -2220,49 +2433,67 @@
|
|
|
2220
2433
|
var bar = document.getElementById('docs-bar');
|
|
2221
2434
|
if (docsBarVisible) {
|
|
2222
2435
|
bar.classList.add('visible');
|
|
2223
|
-
loadDocs();
|
|
2436
|
+
loadDocs(false);
|
|
2224
2437
|
} else {
|
|
2225
2438
|
bar.classList.remove('visible');
|
|
2226
2439
|
docsSelectedFile = null;
|
|
2227
2440
|
}
|
|
2228
2441
|
}
|
|
2229
2442
|
|
|
2230
|
-
function
|
|
2443
|
+
function renderDocsList(data) {
|
|
2444
|
+
var fileList = document.getElementById('docs-file-list');
|
|
2445
|
+
var docs = data.docs || [];
|
|
2446
|
+
document.getElementById('docs-count').textContent = docs.length;
|
|
2447
|
+
if (!docs.length) {
|
|
2448
|
+
fileList.innerHTML = '<div class="docs-loading" style="color:#666;">无文档</div>';
|
|
2449
|
+
return;
|
|
2450
|
+
}
|
|
2451
|
+
var html = '<div style="padding:4px 12px;font-size:11px;color:#666;border-bottom:1px solid #2a2a2a;">📁 ' + escapeHtml(data.cwd || 'PROJECT_DIR') + '</div>';
|
|
2452
|
+
docs.forEach(function(doc) {
|
|
2453
|
+
var activeClass = docsSelectedFile === doc.name ? ' active' : '';
|
|
2454
|
+
var time = new Date(doc.mtime).toLocaleString('zh-CN', { month:'2-digit', day:'2-digit', hour:'2-digit', minute:'2-digit' });
|
|
2455
|
+
html += '<div class="docs-file-item' + activeClass + '" data-file="' + escapeHtml(doc.name) + '">';
|
|
2456
|
+
html += '<span class="docs-file-name">' + escapeHtml(doc.name) + '</span>';
|
|
2457
|
+
html += '<span class="docs-file-time">' + time + '</span>';
|
|
2458
|
+
html += '</div>';
|
|
2459
|
+
});
|
|
2460
|
+
fileList.innerHTML = html;
|
|
2461
|
+
fileList.querySelectorAll('.docs-file-item').forEach(function(item) {
|
|
2462
|
+
item.addEventListener('click', function() {
|
|
2463
|
+
var file = this.getAttribute('data-file');
|
|
2464
|
+
docsSelectedFile = file;
|
|
2465
|
+
fileList.querySelectorAll('.docs-file-item').forEach(function(el) { el.classList.remove('active'); });
|
|
2466
|
+
this.classList.add('active');
|
|
2467
|
+
loadDocContent(file);
|
|
2468
|
+
});
|
|
2469
|
+
});
|
|
2470
|
+
}
|
|
2471
|
+
|
|
2472
|
+
function loadDocs(forceRefresh) {
|
|
2231
2473
|
var fileList = document.getElementById('docs-file-list');
|
|
2232
|
-
|
|
2233
|
-
|
|
2474
|
+
|
|
2475
|
+
if (cachedDocs && !forceRefresh) {
|
|
2476
|
+
renderDocsList(cachedDocs);
|
|
2477
|
+
return;
|
|
2478
|
+
}
|
|
2479
|
+
|
|
2480
|
+
fileList.innerHTML = '<div class="docs-loading">' + (forceRefresh ? '正在刷新...' : '正在查询文档...') + '</div>';
|
|
2481
|
+
document.getElementById('docs-count').textContent = '...';
|
|
2482
|
+
|
|
2483
|
+
if (docsLoading && !forceRefresh) return;
|
|
2484
|
+
docsLoading = true;
|
|
2485
|
+
if (forceRefresh) cachedDocs = null;
|
|
2234
2486
|
|
|
2235
2487
|
fetch(basePath + '/api/docs')
|
|
2236
2488
|
.then(function(r) { return r.json(); })
|
|
2237
2489
|
.then(function(data) {
|
|
2238
|
-
|
|
2239
|
-
|
|
2240
|
-
if (
|
|
2241
|
-
fileList.innerHTML = '<div class="docs-loading" style="color:#666;">无文档</div>';
|
|
2242
|
-
return;
|
|
2243
|
-
}
|
|
2244
|
-
var html = '<div style="padding:4px 12px;font-size:11px;color:#666;border-bottom:1px solid #2a2a2a;">📁 ' + escapeHtml(data.cwd || 'PROJECT_DIR') + '</div>';
|
|
2245
|
-
docs.forEach(function(doc) {
|
|
2246
|
-
var activeClass = docsSelectedFile === doc.name ? ' active' : '';
|
|
2247
|
-
var time = new Date(doc.mtime).toLocaleString('zh-CN', { month:'2-digit', day:'2-digit', hour:'2-digit', minute:'2-digit' });
|
|
2248
|
-
html += '<div class="docs-file-item' + activeClass + '" data-file="' + escapeHtml(doc.name) + '">';
|
|
2249
|
-
html += '<span class="docs-file-name">' + escapeHtml(doc.name) + '</span>';
|
|
2250
|
-
html += '<span class="docs-file-time">' + time + '</span>';
|
|
2251
|
-
html += '</div>';
|
|
2252
|
-
});
|
|
2253
|
-
fileList.innerHTML = html;
|
|
2254
|
-
fileList.querySelectorAll('.docs-file-item').forEach(function(item) {
|
|
2255
|
-
item.addEventListener('click', function() {
|
|
2256
|
-
var file = this.getAttribute('data-file');
|
|
2257
|
-
docsSelectedFile = file;
|
|
2258
|
-
fileList.querySelectorAll('.docs-file-item').forEach(function(el) { el.classList.remove('active'); });
|
|
2259
|
-
this.classList.add('active');
|
|
2260
|
-
loadDocContent(file);
|
|
2261
|
-
});
|
|
2262
|
-
});
|
|
2490
|
+
cachedDocs = data;
|
|
2491
|
+
docsLoading = false;
|
|
2492
|
+
if (docsBarVisible) renderDocsList(data);
|
|
2263
2493
|
})
|
|
2264
2494
|
.catch(function() {
|
|
2265
|
-
|
|
2495
|
+
docsLoading = false;
|
|
2496
|
+
if (docsBarVisible) fileList.innerHTML = '<div class="docs-loading" style="color:#ff6b6b;">加载失败</div>';
|
|
2266
2497
|
});
|
|
2267
2498
|
}
|
|
2268
2499
|
|
|
@@ -2287,7 +2518,7 @@
|
|
|
2287
2518
|
document.getElementById('close-docs').addEventListener('click', toggleDocsBar);
|
|
2288
2519
|
document.getElementById('refresh-docs').addEventListener('click', function(e) {
|
|
2289
2520
|
e.stopPropagation();
|
|
2290
|
-
loadDocs();
|
|
2521
|
+
loadDocs(true);
|
|
2291
2522
|
docsSelectedFile = null;
|
|
2292
2523
|
document.getElementById('docs-content-area').innerHTML =
|
|
2293
2524
|
'<div class="docs-placeholder">' +
|
|
@@ -2301,5 +2532,16 @@
|
|
|
2301
2532
|
setTimeout(resize, 100);
|
|
2302
2533
|
})();
|
|
2303
2534
|
</script>
|
|
2535
|
+
<!-- 启动对话框 -->
|
|
2536
|
+
<div id="startup-overlay">
|
|
2537
|
+
<div id="startup-card">
|
|
2538
|
+
<h3>选择会话</h3>
|
|
2539
|
+
<div id="startup-session-info"></div>
|
|
2540
|
+
<div class="startup-buttons">
|
|
2541
|
+
<button class="startup-btn-restore" id="startup-btn-restore">恢复会话</button>
|
|
2542
|
+
<button class="startup-btn-new" id="startup-btn-new">新建会话</button>
|
|
2543
|
+
</div>
|
|
2544
|
+
</div>
|
|
2545
|
+
</div>
|
|
2304
2546
|
</body>
|
|
2305
2547
|
</html>
|