claude-opencode-viewer 2.6.11 → 2.6.13
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 +10 -83
- package/index.html +171 -96
- package/package.json +1 -1
- package/server.js +76 -19
package/index-pc.html
CHANGED
|
@@ -395,58 +395,6 @@
|
|
|
395
395
|
display: block;
|
|
396
396
|
}
|
|
397
397
|
|
|
398
|
-
#copy-popup {
|
|
399
|
-
display: none;
|
|
400
|
-
position: fixed;
|
|
401
|
-
top: 50%;
|
|
402
|
-
left: 50%;
|
|
403
|
-
transform: translate(-50%, -50%);
|
|
404
|
-
background: #1a1a1a;
|
|
405
|
-
border: 1px solid #444;
|
|
406
|
-
border-radius: 8px;
|
|
407
|
-
padding: 16px;
|
|
408
|
-
z-index: 10000;
|
|
409
|
-
max-width: 80vw;
|
|
410
|
-
max-height: 60vh;
|
|
411
|
-
}
|
|
412
|
-
#copy-popup.show {
|
|
413
|
-
display: flex;
|
|
414
|
-
flex-direction: column;
|
|
415
|
-
gap: 10px;
|
|
416
|
-
}
|
|
417
|
-
#copy-popup textarea {
|
|
418
|
-
width: 500px;
|
|
419
|
-
max-width: 70vw;
|
|
420
|
-
height: 120px;
|
|
421
|
-
background: #0a0a0a;
|
|
422
|
-
color: #ccc;
|
|
423
|
-
border: 1px solid #333;
|
|
424
|
-
border-radius: 4px;
|
|
425
|
-
padding: 8px;
|
|
426
|
-
font-family: Menlo, Monaco, monospace;
|
|
427
|
-
font-size: 12px;
|
|
428
|
-
resize: vertical;
|
|
429
|
-
}
|
|
430
|
-
#copy-popup .popup-actions {
|
|
431
|
-
display: flex;
|
|
432
|
-
justify-content: flex-end;
|
|
433
|
-
gap: 8px;
|
|
434
|
-
}
|
|
435
|
-
#copy-popup button {
|
|
436
|
-
padding: 6px 16px;
|
|
437
|
-
border: none;
|
|
438
|
-
border-radius: 4px;
|
|
439
|
-
cursor: pointer;
|
|
440
|
-
font-size: 13px;
|
|
441
|
-
}
|
|
442
|
-
#copy-popup .btn-copy {
|
|
443
|
-
background: #2563eb;
|
|
444
|
-
color: #fff;
|
|
445
|
-
}
|
|
446
|
-
#copy-popup .btn-close {
|
|
447
|
-
background: #333;
|
|
448
|
-
color: #ccc;
|
|
449
|
-
}
|
|
450
398
|
|
|
451
399
|
/* Git Diff 面板 */
|
|
452
400
|
#git-diff-bar {
|
|
@@ -786,14 +734,6 @@
|
|
|
786
734
|
</div>
|
|
787
735
|
|
|
788
736
|
<div id="copy-toast">已复制</div>
|
|
789
|
-
<div id="copy-popup">
|
|
790
|
-
<div style="color:#ccc;font-size:13px;">剪贴板写入需要 HTTPS,请手动复制:</div>
|
|
791
|
-
<textarea id="copy-popup-text" readonly></textarea>
|
|
792
|
-
<div class="popup-actions">
|
|
793
|
-
<button class="btn-copy" onclick="document.getElementById('copy-popup-text').select();document.execCommand('copy');document.getElementById('copy-popup').classList.remove('show');">选中并复制</button>
|
|
794
|
-
<button class="btn-close" onclick="document.getElementById('copy-popup').classList.remove('show');">关闭</button>
|
|
795
|
-
</div>
|
|
796
|
-
</div>
|
|
797
737
|
<script src="https://cdn.jsdelivr.net/npm/@xterm/xterm@5.5.0/lib/xterm.min.js"></script>
|
|
798
738
|
<script>
|
|
799
739
|
(function() {
|
|
@@ -842,19 +782,15 @@
|
|
|
842
782
|
try {
|
|
843
783
|
var bytes = Uint8Array.from(atob(b64), function(c) { return c.charCodeAt(0); });
|
|
844
784
|
var text = new TextDecoder().decode(bytes);
|
|
845
|
-
|
|
846
|
-
navigator.clipboard.writeText(text).then(function() {
|
|
847
|
-
showCopyToast();
|
|
848
|
-
}).catch(function() {
|
|
849
|
-
showCopyPopup(text);
|
|
850
|
-
});
|
|
851
|
-
} else {
|
|
852
|
-
showCopyPopup(text);
|
|
853
|
-
}
|
|
785
|
+
copyToClipboard(text);
|
|
854
786
|
} catch (e) {}
|
|
855
787
|
return true;
|
|
856
788
|
});
|
|
857
789
|
|
|
790
|
+
// 自动检测反向代理子路径,确保 API/WS 请求带正确前缀
|
|
791
|
+
var basePath = location.pathname.replace(/\/[^/]*$/, '');
|
|
792
|
+
if (basePath === '' || basePath === '/') basePath = '';
|
|
793
|
+
|
|
858
794
|
var modeSelect = document.getElementById('mode-select');
|
|
859
795
|
var terminalEl = document.getElementById('terminal');
|
|
860
796
|
var ws = null;
|
|
@@ -1270,7 +1206,7 @@
|
|
|
1270
1206
|
|
|
1271
1207
|
function connect() {
|
|
1272
1208
|
var proto = location.protocol === 'https:' ? 'wss:' : 'ws:';
|
|
1273
|
-
ws = new WebSocket(proto + '//' + location.host + '/ws');
|
|
1209
|
+
ws = new WebSocket(proto + '//' + location.host + basePath + '/ws');
|
|
1274
1210
|
|
|
1275
1211
|
ws.onopen = function() {
|
|
1276
1212
|
isBufferReplay = true;
|
|
@@ -1416,7 +1352,7 @@
|
|
|
1416
1352
|
var sessionList = document.getElementById('session-list');
|
|
1417
1353
|
sessionList.innerHTML = '<div class="session-loading">加载历史会话...</div>';
|
|
1418
1354
|
|
|
1419
|
-
fetch('/api/sessions')
|
|
1355
|
+
fetch(basePath + '/api/sessions')
|
|
1420
1356
|
.then(function(response) { return response.json(); })
|
|
1421
1357
|
.then(function(data) {
|
|
1422
1358
|
sessions = data;
|
|
@@ -1499,7 +1435,7 @@
|
|
|
1499
1435
|
itemEl.style.opacity = '0.4';
|
|
1500
1436
|
itemEl.style.pointerEvents = 'none';
|
|
1501
1437
|
|
|
1502
|
-
fetch('/api/session/' + sessionId, { method: 'DELETE' })
|
|
1438
|
+
fetch(basePath + '/api/session/' + sessionId, { method: 'DELETE' })
|
|
1503
1439
|
.then(function(r) { return r.json(); })
|
|
1504
1440
|
.then(function(data) {
|
|
1505
1441
|
if (data.ok) {
|
|
@@ -1645,15 +1581,6 @@
|
|
|
1645
1581
|
showCopyToast();
|
|
1646
1582
|
}
|
|
1647
1583
|
|
|
1648
|
-
function showCopyPopup(text) {
|
|
1649
|
-
var popup = document.getElementById('copy-popup');
|
|
1650
|
-
var ta = document.getElementById('copy-popup-text');
|
|
1651
|
-
ta.value = text;
|
|
1652
|
-
popup.classList.add('show');
|
|
1653
|
-
ta.focus();
|
|
1654
|
-
ta.select();
|
|
1655
|
-
}
|
|
1656
|
-
|
|
1657
1584
|
function showCopyToast() {
|
|
1658
1585
|
var toast = document.getElementById('copy-toast');
|
|
1659
1586
|
toast.classList.add('show');
|
|
@@ -1732,7 +1659,7 @@
|
|
|
1732
1659
|
fileList.innerHTML = '<div class="git-diff-loading">加载中...</div>';
|
|
1733
1660
|
document.getElementById('git-diff-count').textContent = '0';
|
|
1734
1661
|
|
|
1735
|
-
fetch('/api/git-status')
|
|
1662
|
+
fetch(basePath + '/api/git-status')
|
|
1736
1663
|
.then(function(r) { return r.json(); })
|
|
1737
1664
|
.then(function(data) {
|
|
1738
1665
|
diffChanges = data.changes || [];
|
|
@@ -1780,7 +1707,7 @@
|
|
|
1780
1707
|
var area = document.getElementById('git-diff-content-area');
|
|
1781
1708
|
area.innerHTML = '<div class="git-diff-loading">加载 diff...</div>';
|
|
1782
1709
|
|
|
1783
|
-
fetch('/api/git-diff?files=' + encodeURIComponent(file))
|
|
1710
|
+
fetch(basePath + '/api/git-diff?files=' + encodeURIComponent(file))
|
|
1784
1711
|
.then(function(r) { return r.json(); })
|
|
1785
1712
|
.then(function(data) {
|
|
1786
1713
|
if (!data.diffs || !data.diffs[0]) {
|
package/index.html
CHANGED
|
@@ -351,71 +351,94 @@
|
|
|
351
351
|
color: #fff;
|
|
352
352
|
}
|
|
353
353
|
|
|
354
|
-
/*
|
|
355
|
-
#
|
|
356
|
-
visibility: hidden;
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
#select-text-layer {
|
|
354
|
+
/* 消息查看器 */
|
|
355
|
+
#message-viewer {
|
|
360
356
|
display: none;
|
|
361
|
-
position:
|
|
357
|
+
position: fixed;
|
|
362
358
|
top: 0; left: 0; right: 0; bottom: 0;
|
|
363
|
-
overflow-y: auto;
|
|
364
|
-
-webkit-overflow-scrolling: touch;
|
|
365
359
|
background: #0a0a0a;
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
z-index: 10;
|
|
360
|
+
z-index: 1000;
|
|
361
|
+
flex-direction: column;
|
|
369
362
|
}
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
display: block;
|
|
363
|
+
#message-viewer.visible {
|
|
364
|
+
display: flex;
|
|
373
365
|
}
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
padding: 6px 0;
|
|
383
|
-
border-bottom: 1px solid #333;
|
|
384
|
-
z-index: 1;
|
|
385
|
-
-webkit-user-select: none;
|
|
386
|
-
user-select: none;
|
|
366
|
+
#msg-viewer-header {
|
|
367
|
+
display: flex;
|
|
368
|
+
align-items: center;
|
|
369
|
+
justify-content: space-between;
|
|
370
|
+
padding: 10px 14px;
|
|
371
|
+
background: #111;
|
|
372
|
+
border-bottom: 1px solid #222;
|
|
373
|
+
flex-shrink: 0;
|
|
387
374
|
}
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
font-family: Menlo, Monaco, "Courier New", monospace;
|
|
393
|
-
font-size: 11px;
|
|
394
|
-
line-height: 1.4;
|
|
395
|
-
white-space: pre-wrap;
|
|
396
|
-
word-break: break-all;
|
|
397
|
-
-webkit-user-select: text;
|
|
398
|
-
user-select: text;
|
|
375
|
+
#msg-viewer-header span {
|
|
376
|
+
color: #ccc;
|
|
377
|
+
font-size: 15px;
|
|
378
|
+
font-weight: 500;
|
|
399
379
|
}
|
|
400
|
-
|
|
401
|
-
#select-mode-close {
|
|
402
|
-
position: absolute;
|
|
403
|
-
top: 6px;
|
|
404
|
-
right: 6px;
|
|
405
|
-
z-index: 20;
|
|
406
|
-
display: none;
|
|
380
|
+
#msg-viewer-close {
|
|
407
381
|
background: rgba(50,50,50,0.9);
|
|
408
382
|
border: 1px solid #555;
|
|
409
383
|
color: #ccc;
|
|
410
|
-
width:
|
|
411
|
-
height:
|
|
384
|
+
width: 30px;
|
|
385
|
+
height: 30px;
|
|
412
386
|
border-radius: 50%;
|
|
413
|
-
font-size:
|
|
414
|
-
line-height:
|
|
387
|
+
font-size: 15px;
|
|
388
|
+
line-height: 28px;
|
|
415
389
|
text-align: center;
|
|
416
390
|
cursor: pointer;
|
|
417
|
-
|
|
418
|
-
|
|
391
|
+
}
|
|
392
|
+
#msg-viewer-content {
|
|
393
|
+
flex: 1;
|
|
394
|
+
overflow-y: auto;
|
|
395
|
+
-webkit-overflow-scrolling: touch;
|
|
396
|
+
padding: 12px;
|
|
397
|
+
}
|
|
398
|
+
.msg-item {
|
|
399
|
+
margin-bottom: 16px;
|
|
400
|
+
padding: 10px 12px;
|
|
401
|
+
border-radius: 8px;
|
|
402
|
+
border: 1px solid #222;
|
|
403
|
+
}
|
|
404
|
+
.msg-user {
|
|
405
|
+
background: #1a2332;
|
|
406
|
+
border-color: #2a4a7c;
|
|
407
|
+
}
|
|
408
|
+
.msg-assistant {
|
|
409
|
+
background: #1a2e1a;
|
|
410
|
+
border-color: #2a5a3a;
|
|
411
|
+
}
|
|
412
|
+
.msg-role {
|
|
413
|
+
font-size: 11px;
|
|
414
|
+
color: #888;
|
|
415
|
+
font-weight: 600;
|
|
416
|
+
text-transform: uppercase;
|
|
417
|
+
margin-bottom: 6px;
|
|
418
|
+
}
|
|
419
|
+
.msg-text {
|
|
420
|
+
color: #ddd;
|
|
421
|
+
font-size: 13px;
|
|
422
|
+
line-height: 1.6;
|
|
423
|
+
white-space: pre-wrap;
|
|
424
|
+
word-break: break-word;
|
|
425
|
+
-webkit-user-select: text;
|
|
426
|
+
user-select: text;
|
|
427
|
+
}
|
|
428
|
+
.msg-tool {
|
|
429
|
+
margin-top: 6px;
|
|
430
|
+
padding: 6px 8px;
|
|
431
|
+
background: #1a1a0a;
|
|
432
|
+
border: 1px solid #333;
|
|
433
|
+
border-radius: 4px;
|
|
434
|
+
font-size: 11px;
|
|
435
|
+
color: #f0ad4e;
|
|
436
|
+
}
|
|
437
|
+
.msg-empty {
|
|
438
|
+
text-align: center;
|
|
439
|
+
padding: 40px 20px;
|
|
440
|
+
color: #666;
|
|
441
|
+
font-size: 13px;
|
|
419
442
|
}
|
|
420
443
|
|
|
421
444
|
/* 复制成功提示 */
|
|
@@ -765,11 +788,6 @@
|
|
|
765
788
|
<div id="content">
|
|
766
789
|
<div id="terminal-container">
|
|
767
790
|
<div id="terminal">
|
|
768
|
-
<div id="select-text-layer">
|
|
769
|
-
<div id="select-hint">长按选择文本 · 点右上角 ✕ 返回终端</div>
|
|
770
|
-
<pre id="select-text-pre"></pre>
|
|
771
|
-
</div>
|
|
772
|
-
<button id="select-mode-close">✕</button>
|
|
773
791
|
</div>
|
|
774
792
|
<div id="virtual-keybar">
|
|
775
793
|
<div class="virtual-key" data-key="up">↑</div>
|
|
@@ -785,6 +803,16 @@
|
|
|
785
803
|
</div>
|
|
786
804
|
</div>
|
|
787
805
|
|
|
806
|
+
<div id="message-viewer">
|
|
807
|
+
<div id="msg-viewer-header">
|
|
808
|
+
<span>会话消息</span>
|
|
809
|
+
<button id="msg-viewer-close">✕</button>
|
|
810
|
+
</div>
|
|
811
|
+
<div id="msg-viewer-content">
|
|
812
|
+
<div class="msg-empty">加载中...</div>
|
|
813
|
+
</div>
|
|
814
|
+
</div>
|
|
815
|
+
|
|
788
816
|
<div id="copy-toast">已复制</div>
|
|
789
817
|
<script src="https://cdn.jsdelivr.net/npm/@xterm/xterm@5.5.0/lib/xterm.min.js"></script>
|
|
790
818
|
<script>
|
|
@@ -850,6 +878,10 @@
|
|
|
850
878
|
return true;
|
|
851
879
|
});
|
|
852
880
|
|
|
881
|
+
// 自动检测反向代理子路径,确保 API/WS 请求带正确前缀
|
|
882
|
+
var basePath = location.pathname.replace(/\/[^/]*$/, '');
|
|
883
|
+
if (basePath === '' || basePath === '/') basePath = '';
|
|
884
|
+
|
|
853
885
|
var modeSelect = document.getElementById('mode-select');
|
|
854
886
|
var terminalEl = document.getElementById('terminal');
|
|
855
887
|
var ws = null;
|
|
@@ -927,7 +959,7 @@
|
|
|
927
959
|
onVVChange();
|
|
928
960
|
}
|
|
929
961
|
|
|
930
|
-
//
|
|
962
|
+
// 移动端固定尺寸计算:基于 #terminal 元素实际高度,确保终端与按钮栏齐平
|
|
931
963
|
function mobileFixedResize() {
|
|
932
964
|
if (!term) return;
|
|
933
965
|
var cellDims = getCellDims();
|
|
@@ -937,12 +969,9 @@
|
|
|
937
969
|
}
|
|
938
970
|
|
|
939
971
|
var padX = 16;
|
|
940
|
-
var
|
|
941
|
-
var topBarHeight = 40;
|
|
942
|
-
var keybarHeight = 52;
|
|
943
|
-
|
|
972
|
+
var termEl = document.getElementById('terminal');
|
|
944
973
|
var availW = window.innerWidth - padX;
|
|
945
|
-
var availH =
|
|
974
|
+
var availH = termEl ? termEl.clientHeight : 300;
|
|
946
975
|
|
|
947
976
|
var currentFontSize = term.options.fontSize;
|
|
948
977
|
var currentCharWidth = cellDims.width;
|
|
@@ -1091,7 +1120,10 @@
|
|
|
1091
1120
|
// 长按检测
|
|
1092
1121
|
var longPressTimer = null;
|
|
1093
1122
|
var longPressTriggered = false;
|
|
1094
|
-
var LONG_PRESS_DELAY =
|
|
1123
|
+
var LONG_PRESS_DELAY = 800; // ms
|
|
1124
|
+
var LONG_PRESS_MOVE_THRESHOLD = 10; // px,移动超过此距离取消长按
|
|
1125
|
+
var touchStartX = 0;
|
|
1126
|
+
var touchStartY = 0;
|
|
1095
1127
|
|
|
1096
1128
|
function clearLongPress() {
|
|
1097
1129
|
if (longPressTimer) {
|
|
@@ -1112,6 +1144,8 @@
|
|
|
1112
1144
|
lastY = e.touches[0].clientY;
|
|
1113
1145
|
lastTime = performance.now();
|
|
1114
1146
|
velocitySamples = [];
|
|
1147
|
+
touchStartX = e.touches[0].clientX;
|
|
1148
|
+
touchStartY = e.touches[0].clientY;
|
|
1115
1149
|
|
|
1116
1150
|
// 启动长按计时器
|
|
1117
1151
|
// 在长按检测期间阻止 xterm textarea 获取焦点,防止弹出键盘
|
|
@@ -1123,14 +1157,18 @@
|
|
|
1123
1157
|
longPressTimer = setTimeout(function() {
|
|
1124
1158
|
longPressTriggered = true;
|
|
1125
1159
|
longPressTimer = null;
|
|
1126
|
-
|
|
1160
|
+
openMessageViewer();
|
|
1127
1161
|
}, LONG_PRESS_DELAY);
|
|
1128
1162
|
}
|
|
1129
1163
|
|
|
1130
1164
|
function handleTouchMove(e) {
|
|
1131
1165
|
if (e.touches.length !== 1) return;
|
|
1132
|
-
//
|
|
1133
|
-
|
|
1166
|
+
// 移动超过阈值才取消长按(容忍手指微小抖动)
|
|
1167
|
+
var dx = e.touches[0].clientX - touchStartX;
|
|
1168
|
+
var dy2 = e.touches[0].clientY - touchStartY;
|
|
1169
|
+
if (Math.abs(dx) > LONG_PRESS_MOVE_THRESHOLD || Math.abs(dy2) > LONG_PRESS_MOVE_THRESHOLD) {
|
|
1170
|
+
clearLongPress();
|
|
1171
|
+
}
|
|
1134
1172
|
var y = e.touches[0].clientY;
|
|
1135
1173
|
var now = performance.now();
|
|
1136
1174
|
var dt = now - lastTime;
|
|
@@ -1327,7 +1365,7 @@
|
|
|
1327
1365
|
|
|
1328
1366
|
function connect() {
|
|
1329
1367
|
var proto = location.protocol === 'https:' ? 'wss:' : 'ws:';
|
|
1330
|
-
ws = new WebSocket(proto + '//' + location.host + '/ws');
|
|
1368
|
+
ws = new WebSocket(proto + '//' + location.host + basePath + '/ws');
|
|
1331
1369
|
|
|
1332
1370
|
ws.onopen = function() {
|
|
1333
1371
|
resize();
|
|
@@ -1578,7 +1616,7 @@
|
|
|
1578
1616
|
var sessionList = document.getElementById('session-list');
|
|
1579
1617
|
sessionList.innerHTML = '<div class="session-loading">加载历史会话...</div>';
|
|
1580
1618
|
|
|
1581
|
-
fetch('/api/sessions')
|
|
1619
|
+
fetch(basePath + '/api/sessions')
|
|
1582
1620
|
.then(function(response) { return response.json(); })
|
|
1583
1621
|
.then(function(data) {
|
|
1584
1622
|
sessions = data;
|
|
@@ -1661,7 +1699,7 @@
|
|
|
1661
1699
|
itemEl.style.opacity = '0.4';
|
|
1662
1700
|
itemEl.style.pointerEvents = 'none';
|
|
1663
1701
|
|
|
1664
|
-
fetch('/api/session/' + sessionId, { method: 'DELETE' })
|
|
1702
|
+
fetch(basePath + '/api/session/' + sessionId, { method: 'DELETE' })
|
|
1665
1703
|
.then(function(r) { return r.json(); })
|
|
1666
1704
|
.then(function(data) {
|
|
1667
1705
|
if (data.ok) {
|
|
@@ -1814,47 +1852,84 @@
|
|
|
1814
1852
|
}
|
|
1815
1853
|
|
|
1816
1854
|
|
|
1817
|
-
//
|
|
1818
|
-
var
|
|
1819
|
-
var
|
|
1820
|
-
var
|
|
1821
|
-
var inSelectMode = false;
|
|
1855
|
+
// 长按打开消息查看器
|
|
1856
|
+
var messageViewer = document.getElementById('message-viewer');
|
|
1857
|
+
var msgViewerContent = document.getElementById('msg-viewer-content');
|
|
1858
|
+
var msgViewerClose = document.getElementById('msg-viewer-close');
|
|
1822
1859
|
|
|
1823
|
-
function
|
|
1824
|
-
if (inSelectMode) return;
|
|
1825
|
-
inSelectMode = true;
|
|
1860
|
+
function openMessageViewer() {
|
|
1826
1861
|
// 收起键盘
|
|
1827
1862
|
var xtermTa = terminalEl.querySelector('.xterm-helper-textarea');
|
|
1828
1863
|
if (xtermTa) xtermTa.blur();
|
|
1829
1864
|
document.activeElement && document.activeElement.blur();
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
selectTextLayer.classList.add('visible');
|
|
1834
|
-
selectModeClose.style.display = 'block';
|
|
1865
|
+
|
|
1866
|
+
messageViewer.classList.add('visible');
|
|
1867
|
+
msgViewerContent.innerHTML = '<div class="msg-empty">加载中...</div>';
|
|
1835
1868
|
unbindTouchScroll();
|
|
1869
|
+
|
|
1870
|
+
// 获取当前会话 ID
|
|
1871
|
+
fetch(basePath + '/api/current-session')
|
|
1872
|
+
.then(function(r) { return r.json(); })
|
|
1873
|
+
.then(function(data) {
|
|
1874
|
+
if (!data.sessionId) {
|
|
1875
|
+
msgViewerContent.innerHTML = '<div class="msg-empty">暂无活跃会话</div>';
|
|
1876
|
+
return;
|
|
1877
|
+
}
|
|
1878
|
+
return fetch(basePath + '/api/session/' + data.sessionId)
|
|
1879
|
+
.then(function(r) { return r.json(); })
|
|
1880
|
+
.then(function(messages) {
|
|
1881
|
+
renderMessages(messages);
|
|
1882
|
+
});
|
|
1883
|
+
})
|
|
1884
|
+
.catch(function(e) {
|
|
1885
|
+
msgViewerContent.innerHTML = '<div class="msg-empty">加载失败: ' + e.message + '</div>';
|
|
1886
|
+
});
|
|
1887
|
+
}
|
|
1888
|
+
|
|
1889
|
+
function renderMessages(messages) {
|
|
1890
|
+
if (!messages || messages.length === 0) {
|
|
1891
|
+
msgViewerContent.innerHTML = '<div class="msg-empty">暂无消息</div>';
|
|
1892
|
+
return;
|
|
1893
|
+
}
|
|
1894
|
+
var html = '';
|
|
1895
|
+
messages.forEach(function(msg) {
|
|
1896
|
+
var role = msg.role || 'unknown';
|
|
1897
|
+
var roleLabel = role === 'user' ? '用户' : role === 'assistant' ? '助手' : role;
|
|
1898
|
+
var cls = role === 'user' ? 'msg-user' : role === 'assistant' ? 'msg-assistant' : '';
|
|
1899
|
+
html += '<div class="msg-item ' + cls + '">';
|
|
1900
|
+
html += '<div class="msg-role">' + roleLabel + '</div>';
|
|
1901
|
+
if (msg.text) {
|
|
1902
|
+
html += '<div class="msg-text">' + escapeHtml(msg.text) + '</div>';
|
|
1903
|
+
}
|
|
1904
|
+
if (msg.toolCalls && msg.toolCalls.length > 0) {
|
|
1905
|
+
msg.toolCalls.forEach(function(tc) {
|
|
1906
|
+
html += '<div class="msg-tool">🔧 ' + escapeHtml(tc.name || 'tool') + '</div>';
|
|
1907
|
+
});
|
|
1908
|
+
}
|
|
1909
|
+
html += '</div>';
|
|
1910
|
+
});
|
|
1911
|
+
msgViewerContent.innerHTML = html;
|
|
1912
|
+
}
|
|
1913
|
+
|
|
1914
|
+
function escapeHtml(str) {
|
|
1915
|
+
return str.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
|
1836
1916
|
}
|
|
1837
1917
|
|
|
1838
|
-
function
|
|
1839
|
-
|
|
1840
|
-
inSelectMode = false;
|
|
1841
|
-
terminalEl.classList.remove('select-mode');
|
|
1842
|
-
selectTextLayer.classList.remove('visible');
|
|
1843
|
-
selectModeClose.style.display = 'none';
|
|
1844
|
-
window.getSelection().removeAllRanges();
|
|
1918
|
+
function closeMessageViewer() {
|
|
1919
|
+
messageViewer.classList.remove('visible');
|
|
1845
1920
|
rebindTouchScroll();
|
|
1846
1921
|
}
|
|
1847
1922
|
|
|
1848
|
-
|
|
1923
|
+
msgViewerClose.addEventListener('click', function(e) {
|
|
1849
1924
|
e.preventDefault();
|
|
1850
1925
|
e.stopPropagation();
|
|
1851
|
-
|
|
1926
|
+
closeMessageViewer();
|
|
1852
1927
|
});
|
|
1853
1928
|
|
|
1854
|
-
|
|
1929
|
+
msgViewerClose.addEventListener('touchend', function(e) {
|
|
1855
1930
|
e.preventDefault();
|
|
1856
1931
|
e.stopPropagation();
|
|
1857
|
-
|
|
1932
|
+
closeMessageViewer();
|
|
1858
1933
|
});
|
|
1859
1934
|
|
|
1860
1935
|
// ======= Git Diff 功能 =======
|
|
@@ -1885,7 +1960,7 @@
|
|
|
1885
1960
|
fileList.innerHTML = '<div class="git-diff-loading">加载中...</div>';
|
|
1886
1961
|
document.getElementById('git-diff-count').textContent = '0';
|
|
1887
1962
|
|
|
1888
|
-
fetch('/api/git-status')
|
|
1963
|
+
fetch(basePath + '/api/git-status')
|
|
1889
1964
|
.then(function(r) { return r.json(); })
|
|
1890
1965
|
.then(function(data) {
|
|
1891
1966
|
diffChanges = data.changes || [];
|
|
@@ -1933,7 +2008,7 @@
|
|
|
1933
2008
|
var area = document.getElementById('git-diff-content-area');
|
|
1934
2009
|
area.innerHTML = '<div class="git-diff-loading">加载 diff...</div>';
|
|
1935
2010
|
|
|
1936
|
-
fetch('/api/git-diff?files=' + encodeURIComponent(file))
|
|
2011
|
+
fetch(basePath + '/api/git-diff?files=' + encodeURIComponent(file))
|
|
1937
2012
|
.then(function(r) { return r.json(); })
|
|
1938
2013
|
.then(function(data) {
|
|
1939
2014
|
if (!data.diffs || !data.diffs[0]) {
|
package/package.json
CHANGED
package/server.js
CHANGED
|
@@ -15,7 +15,7 @@ import Database from 'better-sqlite3';
|
|
|
15
15
|
process.title = 'claude-opencode-viewer';
|
|
16
16
|
|
|
17
17
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
18
|
-
|
|
18
|
+
let PORT = parseInt(process.argv[2]) || 7008;
|
|
19
19
|
const IS_PC = process.argv.includes('--pc');
|
|
20
20
|
const USE_HTTPS = process.argv.includes('--https');
|
|
21
21
|
const JSON_OUTPUT = process.argv.includes('--json');
|
|
@@ -72,6 +72,7 @@ let lastPtyCols = 120;
|
|
|
72
72
|
let lastPtyRows = 30;
|
|
73
73
|
|
|
74
74
|
let activeWs = null;
|
|
75
|
+
let currentSessionId = null;
|
|
75
76
|
const clientSizes = new Map();
|
|
76
77
|
const mobileClients = new Set();
|
|
77
78
|
let currentMode = 'opencode';
|
|
@@ -229,6 +230,30 @@ async function spawnProcess(mode, sessionId = null) {
|
|
|
229
230
|
} catch {}
|
|
230
231
|
}
|
|
231
232
|
opencodeProcess = proc;
|
|
233
|
+
|
|
234
|
+
// 追踪当前会话 ID
|
|
235
|
+
if (sessionId) {
|
|
236
|
+
currentSessionId = sessionId;
|
|
237
|
+
console.log(`[session] 当前会话 ID: ${currentSessionId}`);
|
|
238
|
+
} else {
|
|
239
|
+
// 新建会话:延迟查数据库获取最新 session ID
|
|
240
|
+
currentSessionId = null;
|
|
241
|
+
setTimeout(() => {
|
|
242
|
+
try {
|
|
243
|
+
const db = new Database(OPENCODE_DB_PATH, { readonly: true });
|
|
244
|
+
const row = db.prepare(
|
|
245
|
+
`SELECT id FROM session WHERE parent_id IS NULL AND time_archived IS NULL ORDER BY time_created DESC LIMIT 1`
|
|
246
|
+
).get();
|
|
247
|
+
db.close();
|
|
248
|
+
if (row) {
|
|
249
|
+
currentSessionId = row.id;
|
|
250
|
+
console.log(`[session] 检测到新会话 ID: ${currentSessionId}`);
|
|
251
|
+
}
|
|
252
|
+
} catch (e) {
|
|
253
|
+
console.log('[session] 查询新会话 ID 失败:', e.message);
|
|
254
|
+
}
|
|
255
|
+
}, 3000);
|
|
256
|
+
}
|
|
232
257
|
}
|
|
233
258
|
|
|
234
259
|
currentProcess = proc;
|
|
@@ -479,6 +504,16 @@ const requestHandler = async (req, res) => {
|
|
|
479
504
|
return;
|
|
480
505
|
}
|
|
481
506
|
|
|
507
|
+
// API: 获取当前会话 ID
|
|
508
|
+
if (req.url === '/api/current-session') {
|
|
509
|
+
res.writeHead(200, {
|
|
510
|
+
'Content-Type': 'application/json',
|
|
511
|
+
'Access-Control-Allow-Origin': '*',
|
|
512
|
+
});
|
|
513
|
+
res.end(JSON.stringify({ sessionId: currentSessionId }));
|
|
514
|
+
return;
|
|
515
|
+
}
|
|
516
|
+
|
|
482
517
|
// API: 获取 git status
|
|
483
518
|
if (req.url === '/api/git-status') {
|
|
484
519
|
res.writeHead(200, {
|
|
@@ -795,23 +830,45 @@ wss.on('connection', (ws, req) => {
|
|
|
795
830
|
process.on('SIGINT', () => process.exit(0));
|
|
796
831
|
process.on('SIGTERM', () => process.exit(0));
|
|
797
832
|
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
833
|
+
// PC 模式端口范围,用于端口冲突重试
|
|
834
|
+
const PC_PORT_MIN = 19200;
|
|
835
|
+
const PC_PORT_MAX = 19220;
|
|
836
|
+
const MAX_PORT_RETRIES = 10;
|
|
801
837
|
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
console.log('\n' + '='.repeat(50));
|
|
807
|
-
console.log('✅ Claude OpenCode Viewer 已启动');
|
|
808
|
-
console.log('='.repeat(50));
|
|
809
|
-
console.log(`🖥️ 本地访问:${proto}://127.0.0.1:${PORT}`);
|
|
810
|
-
console.log(`📱 手机访问:${proto}://${ip}:${PORT}`);
|
|
811
|
-
if (USE_HTTPS) console.log('🔐 HTTPS 模式(首次访问需信任自签名证书)');
|
|
812
|
-
console.log('='.repeat(50));
|
|
813
|
-
console.log('\n按 Ctrl+C 停止服务\n');
|
|
814
|
-
}
|
|
838
|
+
function startServer(retries = 0) {
|
|
839
|
+
server.listen(PORT, '0.0.0.0', async () => {
|
|
840
|
+
const ip = getLocalIp();
|
|
841
|
+
const proto = USE_HTTPS ? 'https' : 'http';
|
|
815
842
|
|
|
816
|
-
|
|
817
|
-
});
|
|
843
|
+
if (JSON_OUTPUT) {
|
|
844
|
+
console.log(JSON.stringify({ port: PORT, url: `${proto}://127.0.0.1:${PORT}`, ip, proto, pid: process.pid }));
|
|
845
|
+
} else {
|
|
846
|
+
console.log('\n' + '='.repeat(50));
|
|
847
|
+
console.log('✅ Claude OpenCode Viewer 已启动');
|
|
848
|
+
console.log('='.repeat(50));
|
|
849
|
+
console.log(`🖥️ 本地访问:${proto}://127.0.0.1:${PORT}`);
|
|
850
|
+
console.log(`📱 手机访问:${proto}://${ip}:${PORT}`);
|
|
851
|
+
if (USE_HTTPS) console.log('🔐 HTTPS 模式(首次访问需信任自签名证书)');
|
|
852
|
+
console.log('='.repeat(50));
|
|
853
|
+
console.log('\n按 Ctrl+C 停止服务\n');
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
await spawnProcess('opencode');
|
|
857
|
+
});
|
|
858
|
+
|
|
859
|
+
server.on('error', (err) => {
|
|
860
|
+
if (err.code === 'EADDRINUSE' && IS_PC && retries < MAX_PORT_RETRIES) {
|
|
861
|
+
// PC 模式端口冲突,顺序查找下一个可用端口
|
|
862
|
+
const oldPort = PORT;
|
|
863
|
+
PORT = PORT >= PC_PORT_MAX ? PC_PORT_MIN : PORT + 1;
|
|
864
|
+
console.error(`[port] ${oldPort} 已占用,尝试 ${PORT} (${retries + 1}/${MAX_PORT_RETRIES})`);
|
|
865
|
+
server.removeAllListeners('error');
|
|
866
|
+
startServer(retries + 1);
|
|
867
|
+
} else {
|
|
868
|
+
console.error(`启动失败: ${err.message}`);
|
|
869
|
+
process.exit(1);
|
|
870
|
+
}
|
|
871
|
+
});
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
startServer();
|