claude-opencode-viewer 2.6.43 → 2.6.45
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 +28 -4
- package/index.html +112 -43
- package/package.json +1 -1
- package/server.js +3 -2
package/index-pc.html
CHANGED
|
@@ -365,9 +365,32 @@
|
|
|
365
365
|
overscroll-behavior: contain;
|
|
366
366
|
}
|
|
367
367
|
|
|
368
|
-
#terminal.transitioning {
|
|
369
|
-
|
|
370
|
-
|
|
368
|
+
#terminal.transitioning .xterm { visibility: hidden; }
|
|
369
|
+
@keyframes loading-dots {
|
|
370
|
+
0% { content: ''; }
|
|
371
|
+
25% { content: '.'; }
|
|
372
|
+
50% { content: '..'; }
|
|
373
|
+
75% { content: '...'; }
|
|
374
|
+
}
|
|
375
|
+
#switch-overlay {
|
|
376
|
+
display: none;
|
|
377
|
+
position: absolute;
|
|
378
|
+
top: 0; left: 0; right: 0; bottom: 0;
|
|
379
|
+
background: #0a0a0a;
|
|
380
|
+
color: #ccc;
|
|
381
|
+
align-items: flex-start;
|
|
382
|
+
padding-top: 40px;
|
|
383
|
+
justify-content: center;
|
|
384
|
+
font-size: 15px;
|
|
385
|
+
font-family: -apple-system, BlinkMacSystemFont, sans-serif;
|
|
386
|
+
letter-spacing: 1px;
|
|
387
|
+
z-index: 10;
|
|
388
|
+
}
|
|
389
|
+
#switch-overlay::after {
|
|
390
|
+
content: '';
|
|
391
|
+
animation: loading-dots 1.2s steps(4, end) infinite;
|
|
392
|
+
}
|
|
393
|
+
#terminal.transitioning #switch-overlay { display: flex;
|
|
371
394
|
}
|
|
372
395
|
|
|
373
396
|
|
|
@@ -994,7 +1017,8 @@
|
|
|
994
1017
|
|
|
995
1018
|
<div id="content">
|
|
996
1019
|
<div id="terminal-container">
|
|
997
|
-
<div id="terminal">
|
|
1020
|
+
<div id="terminal" style="position:relative;">
|
|
1021
|
+
<div id="switch-overlay">正在切换</div>
|
|
998
1022
|
<div id="select-text-layer">
|
|
999
1023
|
<div id="select-hint">长按选择文本 · 点右上角 ✕ 返回终端</div>
|
|
1000
1024
|
<pre id="select-text-pre"></pre>
|
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; }
|
|
@@ -28,7 +54,8 @@
|
|
|
28
54
|
background: #111;
|
|
29
55
|
border-bottom: 1px solid #222;
|
|
30
56
|
display: flex;
|
|
31
|
-
align-items:
|
|
57
|
+
align-items: flex-start;
|
|
58
|
+
padding-top: 40px;
|
|
32
59
|
justify-content: space-between;
|
|
33
60
|
flex-shrink: 0;
|
|
34
61
|
height: 40px;
|
|
@@ -64,7 +91,8 @@
|
|
|
64
91
|
|
|
65
92
|
#session-history-header {
|
|
66
93
|
display: flex;
|
|
67
|
-
align-items:
|
|
94
|
+
align-items: flex-start;
|
|
95
|
+
padding-top: 40px;
|
|
68
96
|
justify-content: space-between;
|
|
69
97
|
padding: 12px 16px;
|
|
70
98
|
background: #111;
|
|
@@ -98,7 +126,8 @@
|
|
|
98
126
|
|
|
99
127
|
.session-item {
|
|
100
128
|
display: flex;
|
|
101
|
-
align-items:
|
|
129
|
+
align-items: flex-start;
|
|
130
|
+
padding-top: 40px;
|
|
102
131
|
gap: 8px;
|
|
103
132
|
padding: 8px 12px;
|
|
104
133
|
background: #1a1a1a;
|
|
@@ -182,7 +211,8 @@
|
|
|
182
211
|
cursor: pointer;
|
|
183
212
|
border-radius: 4px;
|
|
184
213
|
display: flex;
|
|
185
|
-
align-items:
|
|
214
|
+
align-items: flex-start;
|
|
215
|
+
padding-top: 40px;
|
|
186
216
|
gap: 3px;
|
|
187
217
|
flex-shrink: 0;
|
|
188
218
|
}
|
|
@@ -223,7 +253,8 @@
|
|
|
223
253
|
cursor: pointer;
|
|
224
254
|
border-radius: 50%;
|
|
225
255
|
display: flex;
|
|
226
|
-
align-items:
|
|
256
|
+
align-items: flex-start;
|
|
257
|
+
padding-top: 40px;
|
|
227
258
|
justify-content: center;
|
|
228
259
|
transition: all 0.15s;
|
|
229
260
|
-webkit-tap-highlight-color: transparent;
|
|
@@ -268,7 +299,8 @@
|
|
|
268
299
|
|
|
269
300
|
#claude-detail-header {
|
|
270
301
|
display: flex;
|
|
271
|
-
align-items:
|
|
302
|
+
align-items: flex-start;
|
|
303
|
+
padding-top: 40px;
|
|
272
304
|
padding: 10px 12px;
|
|
273
305
|
background: #111;
|
|
274
306
|
border-bottom: 1px solid #222;
|
|
@@ -321,7 +353,8 @@
|
|
|
321
353
|
#mode-switcher {
|
|
322
354
|
display: flex;
|
|
323
355
|
gap: 4px;
|
|
324
|
-
align-items:
|
|
356
|
+
align-items: flex-start;
|
|
357
|
+
padding-top: 40px;
|
|
325
358
|
}
|
|
326
359
|
|
|
327
360
|
#mode-label {
|
|
@@ -359,19 +392,6 @@
|
|
|
359
392
|
background: #0a0a0a;
|
|
360
393
|
position: relative;
|
|
361
394
|
}
|
|
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
395
|
#terminal {
|
|
376
396
|
flex: 1;
|
|
377
397
|
overflow: hidden;
|
|
@@ -380,10 +400,26 @@
|
|
|
380
400
|
overscroll-behavior: contain;
|
|
381
401
|
}
|
|
382
402
|
|
|
383
|
-
#terminal.transitioning {
|
|
384
|
-
|
|
385
|
-
|
|
403
|
+
#terminal.transitioning .xterm { visibility: hidden; }
|
|
404
|
+
#switch-overlay {
|
|
405
|
+
display: none;
|
|
406
|
+
position: absolute;
|
|
407
|
+
top: 0; left: 0; right: 0; bottom: 0;
|
|
408
|
+
background: #0a0a0a;
|
|
409
|
+
color: #ccc;
|
|
410
|
+
align-items: flex-start;
|
|
411
|
+
padding-top: 40px;
|
|
412
|
+
justify-content: center;
|
|
413
|
+
font-size: 16px;
|
|
414
|
+
font-family: -apple-system, BlinkMacSystemFont, sans-serif;
|
|
415
|
+
letter-spacing: 1px;
|
|
416
|
+
z-index: 10;
|
|
417
|
+
}
|
|
418
|
+
#switch-overlay::after {
|
|
419
|
+
content: '';
|
|
420
|
+
animation: loading-dots 1.2s steps(4, end) infinite;
|
|
386
421
|
}
|
|
422
|
+
#terminal.transitioning #switch-overlay { display: flex; }
|
|
387
423
|
|
|
388
424
|
/* 参考 cc-viewer 的 TerminalPanel.module.css: 虚拟键盘栏 */
|
|
389
425
|
#virtual-keybar {
|
|
@@ -413,7 +449,8 @@
|
|
|
413
449
|
min-width: 44px;
|
|
414
450
|
min-height: 44px;
|
|
415
451
|
display: flex;
|
|
416
|
-
align-items:
|
|
452
|
+
align-items: flex-start;
|
|
453
|
+
padding-top: 40px;
|
|
417
454
|
justify-content: center;
|
|
418
455
|
border: none;
|
|
419
456
|
outline: none;
|
|
@@ -442,7 +479,8 @@
|
|
|
442
479
|
}
|
|
443
480
|
#msg-viewer-header {
|
|
444
481
|
display: flex;
|
|
445
|
-
align-items:
|
|
482
|
+
align-items: flex-start;
|
|
483
|
+
padding-top: 40px;
|
|
446
484
|
justify-content: space-between;
|
|
447
485
|
padding: 10px 14px;
|
|
448
486
|
background: #111;
|
|
@@ -569,7 +607,8 @@
|
|
|
569
607
|
|
|
570
608
|
#git-diff-header {
|
|
571
609
|
display: flex;
|
|
572
|
-
align-items:
|
|
610
|
+
align-items: flex-start;
|
|
611
|
+
padding-top: 40px;
|
|
573
612
|
justify-content: space-between;
|
|
574
613
|
padding: 12px 16px;
|
|
575
614
|
background: #111;
|
|
@@ -593,7 +632,8 @@
|
|
|
593
632
|
|
|
594
633
|
.git-diff-file-item {
|
|
595
634
|
display: flex;
|
|
596
|
-
align-items:
|
|
635
|
+
align-items: flex-start;
|
|
636
|
+
padding-top: 40px;
|
|
597
637
|
padding: 6px 12px;
|
|
598
638
|
cursor: pointer;
|
|
599
639
|
color: #ccc;
|
|
@@ -637,7 +677,8 @@
|
|
|
637
677
|
|
|
638
678
|
.git-diff-content-header {
|
|
639
679
|
display: flex;
|
|
640
|
-
align-items:
|
|
680
|
+
align-items: flex-start;
|
|
681
|
+
padding-top: 40px;
|
|
641
682
|
gap: 10px;
|
|
642
683
|
padding: 8px 12px;
|
|
643
684
|
border-bottom: 1px solid #2a2a2a;
|
|
@@ -677,7 +718,8 @@
|
|
|
677
718
|
flex: 1;
|
|
678
719
|
display: flex;
|
|
679
720
|
flex-direction: column;
|
|
680
|
-
align-items:
|
|
721
|
+
align-items: flex-start;
|
|
722
|
+
padding-top: 40px;
|
|
681
723
|
justify-content: center;
|
|
682
724
|
gap: 12px;
|
|
683
725
|
color: #333;
|
|
@@ -710,7 +752,8 @@
|
|
|
710
752
|
#docs-bar.visible { display: flex; }
|
|
711
753
|
#docs-header {
|
|
712
754
|
display: flex;
|
|
713
|
-
align-items:
|
|
755
|
+
align-items: flex-start;
|
|
756
|
+
padding-top: 40px;
|
|
714
757
|
justify-content: space-between;
|
|
715
758
|
padding: 12px 16px;
|
|
716
759
|
background: #111;
|
|
@@ -730,7 +773,8 @@
|
|
|
730
773
|
}
|
|
731
774
|
.docs-file-item {
|
|
732
775
|
display: flex;
|
|
733
|
-
align-items:
|
|
776
|
+
align-items: flex-start;
|
|
777
|
+
padding-top: 40px;
|
|
734
778
|
padding: 8px 12px;
|
|
735
779
|
cursor: pointer;
|
|
736
780
|
color: #ccc;
|
|
@@ -776,7 +820,8 @@
|
|
|
776
820
|
flex: 1;
|
|
777
821
|
display: flex;
|
|
778
822
|
flex-direction: column;
|
|
779
|
-
align-items:
|
|
823
|
+
align-items: flex-start;
|
|
824
|
+
padding-top: 40px;
|
|
780
825
|
justify-content: center;
|
|
781
826
|
gap: 12px;
|
|
782
827
|
color: #333;
|
|
@@ -857,7 +902,7 @@
|
|
|
857
902
|
</head>
|
|
858
903
|
<body>
|
|
859
904
|
<!-- 参考 cc-viewer 的 App.jsx 行 1315-1607: 完整的移动端布局结构 -->
|
|
860
|
-
<div id="loading-overlay"
|
|
905
|
+
<div id="loading-overlay">正在初始化</div>
|
|
861
906
|
<div id="layout">
|
|
862
907
|
<div id="header">
|
|
863
908
|
<div style="display: flex; gap: 4px; align-items: center; overflow-x: auto; flex: 1; min-width: 0;">
|
|
@@ -1041,7 +1086,8 @@
|
|
|
1041
1086
|
|
|
1042
1087
|
<div id="content">
|
|
1043
1088
|
<div id="terminal-container">
|
|
1044
|
-
<div id="terminal">
|
|
1089
|
+
<div id="terminal" style="position:relative;">
|
|
1090
|
+
<div id="switch-overlay">正在切换</div>
|
|
1045
1091
|
</div>
|
|
1046
1092
|
<div id="virtual-keybar">
|
|
1047
1093
|
<div class="virtual-key" data-key="up">↑</div>
|
|
@@ -1587,6 +1633,7 @@
|
|
|
1587
1633
|
ws = new WebSocket(proto + '//' + location.host + basePath + '/ws');
|
|
1588
1634
|
|
|
1589
1635
|
ws.onopen = function() {
|
|
1636
|
+
setLoadingText('正在连接服务');
|
|
1590
1637
|
resize();
|
|
1591
1638
|
rebindTouchScroll();
|
|
1592
1639
|
};
|
|
@@ -1599,9 +1646,24 @@
|
|
|
1599
1646
|
};
|
|
1600
1647
|
|
|
1601
1648
|
var loadingOverlay = document.getElementById('loading-overlay');
|
|
1602
|
-
|
|
1649
|
+
var loadingShowTime = Date.now();
|
|
1650
|
+
var loadingMinMs = 600;
|
|
1651
|
+
var loadingHideTimer = null;
|
|
1652
|
+
function setLoadingText(text) {
|
|
1603
1653
|
if (loadingOverlay && !loadingOverlay.classList.contains('hidden')) {
|
|
1654
|
+
loadingOverlay.textContent = text;
|
|
1655
|
+
}
|
|
1656
|
+
}
|
|
1657
|
+
function hideLoading() {
|
|
1658
|
+
if (!loadingOverlay || loadingOverlay.classList.contains('hidden')) return;
|
|
1659
|
+
if (loadingHideTimer) return; // 已在等待中
|
|
1660
|
+
var elapsed = Date.now() - loadingShowTime;
|
|
1661
|
+
if (elapsed >= loadingMinMs) {
|
|
1604
1662
|
loadingOverlay.classList.add('hidden');
|
|
1663
|
+
} else {
|
|
1664
|
+
loadingHideTimer = setTimeout(function() {
|
|
1665
|
+
loadingOverlay.classList.add('hidden');
|
|
1666
|
+
}, loadingMinMs - elapsed);
|
|
1605
1667
|
}
|
|
1606
1668
|
}
|
|
1607
1669
|
|
|
@@ -1621,12 +1683,25 @@
|
|
|
1621
1683
|
}
|
|
1622
1684
|
}
|
|
1623
1685
|
else if (msg.type === 'state') {
|
|
1686
|
+
// 重连时清掉旧终端内容(如"连接断开"提示)
|
|
1687
|
+
if (writeTimer) { cancelAnimationFrame(writeTimer); writeTimer = null; }
|
|
1688
|
+
writeBuffer = '';
|
|
1689
|
+
term.reset();
|
|
1690
|
+
term.clear();
|
|
1691
|
+
// 同步模式 UI
|
|
1692
|
+
if (msg.mode) {
|
|
1693
|
+
currentMode = msg.mode;
|
|
1694
|
+
modeSelect.value = msg.mode;
|
|
1695
|
+
}
|
|
1624
1696
|
if (msg.running) {
|
|
1697
|
+
setLoadingText('正在连接');
|
|
1698
|
+
hideLoading();
|
|
1625
1699
|
preloadData();
|
|
1626
1700
|
}
|
|
1627
1701
|
// 服务端还没启动进程时,查最近会话并自动恢复
|
|
1628
1702
|
if (!msg.running && !mobileInitSent) {
|
|
1629
1703
|
mobileInitSent = true;
|
|
1704
|
+
setLoadingText('正在查询会话');
|
|
1630
1705
|
fetch('/api/last-sessions')
|
|
1631
1706
|
.then(function(r) { return r.json(); })
|
|
1632
1707
|
.then(function(data) {
|
|
@@ -1641,6 +1716,7 @@
|
|
|
1641
1716
|
claudeProject = cl.project;
|
|
1642
1717
|
}
|
|
1643
1718
|
currentMode = mode;
|
|
1719
|
+
setLoadingText('正在启动 ' + (mode === 'claude' ? 'Claude' : 'OpenCode'));
|
|
1644
1720
|
ws.send(JSON.stringify({ type: 'init', mode: mode, sessionId: sessionId || null }));
|
|
1645
1721
|
})
|
|
1646
1722
|
.catch(function() {
|
|
@@ -1663,13 +1739,6 @@
|
|
|
1663
1739
|
writeBuffer = '';
|
|
1664
1740
|
term.clear();
|
|
1665
1741
|
}
|
|
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
1742
|
else if (msg.type === 'restored') {
|
|
1674
1743
|
// 会话恢复成功,清除所有残留
|
|
1675
1744
|
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 }));
|