claude-opencode-viewer 2.6.43 → 2.6.44
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.html +59 -23
- package/package.json +1 -1
- package/server.js +3 -2
package/index.html
CHANGED
|
@@ -4,7 +4,33 @@
|
|
|
4
4
|
<meta charset="UTF-8" />
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover">
|
|
6
6
|
<title>Claude OpenCode Viewer</title>
|
|
7
|
-
<
|
|
7
|
+
<style id="loading-style">
|
|
8
|
+
@keyframes loading-dots {
|
|
9
|
+
0% { content: ''; }
|
|
10
|
+
25% { content: '.'; }
|
|
11
|
+
50% { content: '..'; }
|
|
12
|
+
75% { content: '...'; }
|
|
13
|
+
}
|
|
14
|
+
#loading-overlay {
|
|
15
|
+
position: fixed;
|
|
16
|
+
top: 0; left: 0; right: 0; bottom: 0;
|
|
17
|
+
background: #0a0a0a;
|
|
18
|
+
color: #ccc;
|
|
19
|
+
display: flex;
|
|
20
|
+
align-items: center;
|
|
21
|
+
justify-content: center;
|
|
22
|
+
font-size: 16px;
|
|
23
|
+
font-family: -apple-system, BlinkMacSystemFont, sans-serif;
|
|
24
|
+
letter-spacing: 1px;
|
|
25
|
+
z-index: 99999;
|
|
26
|
+
}
|
|
27
|
+
#loading-overlay::after {
|
|
28
|
+
content: '';
|
|
29
|
+
animation: loading-dots 1.2s steps(4, end) infinite;
|
|
30
|
+
}
|
|
31
|
+
#loading-overlay.hidden { display: none !important; }
|
|
32
|
+
</style>
|
|
33
|
+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@xterm/xterm@5.5.0/css/xterm.min.css" media="print" onload="this.media='all'">
|
|
8
34
|
<style>
|
|
9
35
|
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
10
36
|
html, body { margin: 0; padding: 0; overflow: hidden; }
|
|
@@ -359,19 +385,6 @@
|
|
|
359
385
|
background: #0a0a0a;
|
|
360
386
|
position: relative;
|
|
361
387
|
}
|
|
362
|
-
#loading-overlay {
|
|
363
|
-
position: fixed;
|
|
364
|
-
top: 0; left: 0; right: 0; bottom: 0;
|
|
365
|
-
background: #0a0a0a;
|
|
366
|
-
color: #888;
|
|
367
|
-
display: flex;
|
|
368
|
-
align-items: center;
|
|
369
|
-
justify-content: center;
|
|
370
|
-
font-size: 15px;
|
|
371
|
-
z-index: 9999;
|
|
372
|
-
}
|
|
373
|
-
#loading-overlay.hidden { display: none; }
|
|
374
|
-
|
|
375
388
|
#terminal {
|
|
376
389
|
flex: 1;
|
|
377
390
|
overflow: hidden;
|
|
@@ -857,7 +870,7 @@
|
|
|
857
870
|
</head>
|
|
858
871
|
<body>
|
|
859
872
|
<!-- 参考 cc-viewer 的 App.jsx 行 1315-1607: 完整的移动端布局结构 -->
|
|
860
|
-
<div id="loading-overlay"
|
|
873
|
+
<div id="loading-overlay">正在初始化</div>
|
|
861
874
|
<div id="layout">
|
|
862
875
|
<div id="header">
|
|
863
876
|
<div style="display: flex; gap: 4px; align-items: center; overflow-x: auto; flex: 1; min-width: 0;">
|
|
@@ -1587,6 +1600,7 @@
|
|
|
1587
1600
|
ws = new WebSocket(proto + '//' + location.host + basePath + '/ws');
|
|
1588
1601
|
|
|
1589
1602
|
ws.onopen = function() {
|
|
1603
|
+
setLoadingText('正在连接服务');
|
|
1590
1604
|
resize();
|
|
1591
1605
|
rebindTouchScroll();
|
|
1592
1606
|
};
|
|
@@ -1599,9 +1613,24 @@
|
|
|
1599
1613
|
};
|
|
1600
1614
|
|
|
1601
1615
|
var loadingOverlay = document.getElementById('loading-overlay');
|
|
1602
|
-
|
|
1616
|
+
var loadingShowTime = Date.now();
|
|
1617
|
+
var loadingMinMs = 600;
|
|
1618
|
+
var loadingHideTimer = null;
|
|
1619
|
+
function setLoadingText(text) {
|
|
1603
1620
|
if (loadingOverlay && !loadingOverlay.classList.contains('hidden')) {
|
|
1621
|
+
loadingOverlay.textContent = text;
|
|
1622
|
+
}
|
|
1623
|
+
}
|
|
1624
|
+
function hideLoading() {
|
|
1625
|
+
if (!loadingOverlay || loadingOverlay.classList.contains('hidden')) return;
|
|
1626
|
+
if (loadingHideTimer) return; // 已在等待中
|
|
1627
|
+
var elapsed = Date.now() - loadingShowTime;
|
|
1628
|
+
if (elapsed >= loadingMinMs) {
|
|
1604
1629
|
loadingOverlay.classList.add('hidden');
|
|
1630
|
+
} else {
|
|
1631
|
+
loadingHideTimer = setTimeout(function() {
|
|
1632
|
+
loadingOverlay.classList.add('hidden');
|
|
1633
|
+
}, loadingMinMs - elapsed);
|
|
1605
1634
|
}
|
|
1606
1635
|
}
|
|
1607
1636
|
|
|
@@ -1621,12 +1650,25 @@
|
|
|
1621
1650
|
}
|
|
1622
1651
|
}
|
|
1623
1652
|
else if (msg.type === 'state') {
|
|
1653
|
+
// 重连时清掉旧终端内容(如"连接断开"提示)
|
|
1654
|
+
if (writeTimer) { cancelAnimationFrame(writeTimer); writeTimer = null; }
|
|
1655
|
+
writeBuffer = '';
|
|
1656
|
+
term.reset();
|
|
1657
|
+
term.clear();
|
|
1658
|
+
// 同步模式 UI
|
|
1659
|
+
if (msg.mode) {
|
|
1660
|
+
currentMode = msg.mode;
|
|
1661
|
+
modeSelect.value = msg.mode;
|
|
1662
|
+
}
|
|
1624
1663
|
if (msg.running) {
|
|
1664
|
+
setLoadingText('正在连接');
|
|
1665
|
+
hideLoading();
|
|
1625
1666
|
preloadData();
|
|
1626
1667
|
}
|
|
1627
1668
|
// 服务端还没启动进程时,查最近会话并自动恢复
|
|
1628
1669
|
if (!msg.running && !mobileInitSent) {
|
|
1629
1670
|
mobileInitSent = true;
|
|
1671
|
+
setLoadingText('正在查询会话');
|
|
1630
1672
|
fetch('/api/last-sessions')
|
|
1631
1673
|
.then(function(r) { return r.json(); })
|
|
1632
1674
|
.then(function(data) {
|
|
@@ -1641,6 +1683,7 @@
|
|
|
1641
1683
|
claudeProject = cl.project;
|
|
1642
1684
|
}
|
|
1643
1685
|
currentMode = mode;
|
|
1686
|
+
setLoadingText('正在启动 ' + (mode === 'claude' ? 'Claude' : 'OpenCode'));
|
|
1644
1687
|
ws.send(JSON.stringify({ type: 'init', mode: mode, sessionId: sessionId || null }));
|
|
1645
1688
|
})
|
|
1646
1689
|
.catch(function() {
|
|
@@ -1663,13 +1706,6 @@
|
|
|
1663
1706
|
writeBuffer = '';
|
|
1664
1707
|
term.clear();
|
|
1665
1708
|
}
|
|
1666
|
-
else if (msg.type === 'state') {
|
|
1667
|
-
if (msg.mode) {
|
|
1668
|
-
currentMode = msg.mode;
|
|
1669
|
-
modeSelect.value = msg.mode;
|
|
1670
|
-
modeIndicator.textContent = msg.mode === 'claude' ? 'Claude' : 'OpenCode';
|
|
1671
|
-
}
|
|
1672
|
-
}
|
|
1673
1709
|
else if (msg.type === 'restored') {
|
|
1674
1710
|
// 会话恢复成功,清除所有残留
|
|
1675
1711
|
if (writeTimer) { cancelAnimationFrame(writeTimer); writeTimer = null; }
|
package/package.json
CHANGED
package/server.js
CHANGED
|
@@ -295,6 +295,8 @@ async function spawnProcess(mode, sessionId = null) {
|
|
|
295
295
|
});
|
|
296
296
|
|
|
297
297
|
proc.onData((data) => {
|
|
298
|
+
// 忽略已被替换的旧进程输出
|
|
299
|
+
if (currentProcess !== proc) return;
|
|
298
300
|
outputBuffer += data;
|
|
299
301
|
if (outputBuffer.length > MAX_BUFFER) {
|
|
300
302
|
const rawStart = outputBuffer.length - MAX_BUFFER;
|
|
@@ -1216,8 +1218,7 @@ wssInst.on('connection', (ws, req) => {
|
|
|
1216
1218
|
outputBuffer = '';
|
|
1217
1219
|
try {
|
|
1218
1220
|
await spawnProcess(mode, msg.sessionId || null);
|
|
1219
|
-
ws.send(JSON.stringify({ type: '
|
|
1220
|
-
ws.send(JSON.stringify({ type: 'started', sessionId: msg.sessionId || null }));
|
|
1221
|
+
ws.send(JSON.stringify({ type: 'started', mode: currentMode, sessionId: msg.sessionId || null }));
|
|
1221
1222
|
} catch (e) {
|
|
1222
1223
|
LOG('[init] 启动失败:', e.message);
|
|
1223
1224
|
ws.send(JSON.stringify({ type: 'start-error', error: e.message }));
|