claude-opencode-viewer 2.6.48 → 2.6.49
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 +81 -34
- package/index.html +74 -34
- package/package.json +1 -1
- package/server.js +78 -7
- package/test-doc.md +86 -0
package/index-pc.html
CHANGED
|
@@ -740,6 +740,39 @@
|
|
|
740
740
|
line-height: 1.6;
|
|
741
741
|
color: #d4d4d4;
|
|
742
742
|
}
|
|
743
|
+
.docs-md-content {
|
|
744
|
+
font-size: 13px; line-height: 1.7; color: #d4d4d4; word-break: break-word;
|
|
745
|
+
}
|
|
746
|
+
.docs-md-content h1, .docs-md-content h2, .docs-md-content h3 {
|
|
747
|
+
margin: 16px 0 8px; color: #e0e0e0; border-bottom: 1px solid #333; padding-bottom: 4px;
|
|
748
|
+
}
|
|
749
|
+
.docs-md-content h1 { font-size: 1.4em; }
|
|
750
|
+
.docs-md-content h2 { font-size: 1.2em; }
|
|
751
|
+
.docs-md-content h3 { font-size: 1.05em; }
|
|
752
|
+
.docs-md-content p { margin: 8px 0; }
|
|
753
|
+
.docs-md-content pre {
|
|
754
|
+
background: #1a1a1a; border: 1px solid #333; border-radius: 4px;
|
|
755
|
+
padding: 10px; overflow-x: auto; margin: 8px 0; white-space: pre-wrap; word-break: break-word;
|
|
756
|
+
}
|
|
757
|
+
.docs-md-content code {
|
|
758
|
+
background: #1a1a1a; padding: 1px 4px; border-radius: 3px; font-size: 12px;
|
|
759
|
+
font-family: Menlo, Monaco, monospace;
|
|
760
|
+
}
|
|
761
|
+
.docs-md-content pre code { background: none; padding: 0; }
|
|
762
|
+
.docs-md-content ul, .docs-md-content ol { margin: 8px 0; padding-left: 20px; }
|
|
763
|
+
.docs-md-content li { margin: 4px 0; }
|
|
764
|
+
.docs-md-content blockquote {
|
|
765
|
+
border-left: 3px solid #444; margin: 8px 0; padding: 4px 12px; color: #999;
|
|
766
|
+
}
|
|
767
|
+
.docs-md-content table { border-collapse: collapse; margin: 8px 0; width: 100%; }
|
|
768
|
+
.docs-md-content th, .docs-md-content td {
|
|
769
|
+
border: 1px solid #333; padding: 6px 10px; text-align: left;
|
|
770
|
+
}
|
|
771
|
+
.docs-md-content th { background: #1a1a1a; }
|
|
772
|
+
.docs-md-content a { color: #58a6ff; text-decoration: none; }
|
|
773
|
+
.docs-md-content a:hover { text-decoration: underline; }
|
|
774
|
+
.docs-md-content img { max-width: 100%; }
|
|
775
|
+
.docs-md-content hr { border: none; border-top: 1px solid #333; margin: 12px 0; }
|
|
743
776
|
.docs-placeholder {
|
|
744
777
|
flex: 1;
|
|
745
778
|
display: flex;
|
|
@@ -805,6 +838,13 @@
|
|
|
805
838
|
color: #ffa198;
|
|
806
839
|
}
|
|
807
840
|
|
|
841
|
+
.diff-sign {
|
|
842
|
+
display: inline-block;
|
|
843
|
+
width: 12px;
|
|
844
|
+
font-weight: bold;
|
|
845
|
+
user-select: none;
|
|
846
|
+
}
|
|
847
|
+
|
|
808
848
|
.diff-line-hunk {
|
|
809
849
|
background: rgba(56, 139, 253, 0.1);
|
|
810
850
|
}
|
|
@@ -1071,6 +1111,8 @@
|
|
|
1071
1111
|
<div id="copy-toast">已复制</div>
|
|
1072
1112
|
<script src="https://cdn.jsdelivr.net/npm/@xterm/xterm@5.5.0/lib/xterm.min.js"></script>
|
|
1073
1113
|
<script src="https://cdn.jsdelivr.net/npm/@xterm/addon-webgl@0.18.0/lib/addon-webgl.min.js"></script>
|
|
1114
|
+
<script src="https://cdn.jsdelivr.net/npm/marked@15.0.7/marked.min.js"></script>
|
|
1115
|
+
<script src="https://cdn.jsdelivr.net/npm/dompurify@3.2.4/dist/purify.min.js"></script>
|
|
1074
1116
|
<script>
|
|
1075
1117
|
(function() {
|
|
1076
1118
|
var isMobile = /Mobi|Android|iPhone|iPad|iPod/i.test(navigator.userAgent);
|
|
@@ -1724,6 +1766,11 @@
|
|
|
1724
1766
|
term.clear();
|
|
1725
1767
|
}
|
|
1726
1768
|
else if (msg.type === 'state') {
|
|
1769
|
+
// 重连时清掉旧终端内容,防止重复叠加
|
|
1770
|
+
if (writeTimer) { cancelAnimationFrame(writeTimer); writeTimer = null; }
|
|
1771
|
+
writeBuffer = '';
|
|
1772
|
+
term.reset();
|
|
1773
|
+
term.clear();
|
|
1727
1774
|
if (msg.mode) {
|
|
1728
1775
|
currentMode = msg.mode;
|
|
1729
1776
|
modeSelect.value = msg.mode;
|
|
@@ -2432,38 +2479,29 @@
|
|
|
2432
2479
|
|
|
2433
2480
|
function loadDiffContent(file) {
|
|
2434
2481
|
var area = document.getElementById('git-diff-content-area');
|
|
2435
|
-
|
|
2436
|
-
|
|
2437
|
-
|
|
2438
|
-
|
|
2439
|
-
|
|
2440
|
-
|
|
2441
|
-
|
|
2442
|
-
|
|
2443
|
-
|
|
2444
|
-
|
|
2445
|
-
|
|
2446
|
-
|
|
2447
|
-
|
|
2448
|
-
|
|
2449
|
-
|
|
2450
|
-
|
|
2451
|
-
|
|
2452
|
-
|
|
2453
|
-
|
|
2454
|
-
|
|
2455
|
-
} else if (d.unified_diff) {
|
|
2456
|
-
html += renderUnifiedDiff(d.unified_diff);
|
|
2457
|
-
} else {
|
|
2458
|
-
html += '<div class="git-diff-loading" style="color:#666;">无变更内容</div>';
|
|
2459
|
-
}
|
|
2482
|
+
var d = diffChanges.find(function(c) { return c.file === file; });
|
|
2483
|
+
if (!d) {
|
|
2484
|
+
area.innerHTML = '<div class="git-diff-error">无 diff 数据</div>';
|
|
2485
|
+
return;
|
|
2486
|
+
}
|
|
2487
|
+
var html = '<div class="git-diff-content-header">';
|
|
2488
|
+
html += '<span class="git-diff-content-path">' + escapeHtml(d.file) + '</span>';
|
|
2489
|
+
html += '<span class="git-diff-badge">' + (d.is_new ? 'NEW' : d.is_deleted ? 'DEL' : 'DIFF') + '</span>';
|
|
2490
|
+
html += '</div>';
|
|
2491
|
+
html += '<div class="git-diff-content-scroll">';
|
|
2492
|
+
|
|
2493
|
+
if (d.is_binary) {
|
|
2494
|
+
html += '<div class="git-diff-loading" style="font-style:italic;">二进制文件</div>';
|
|
2495
|
+
} else if (d.is_large) {
|
|
2496
|
+
html += '<div class="git-diff-loading" style="color:#e2c08d;">文件过大: ' + (d.size / (1024 * 1024)).toFixed(2) + ' MB</div>';
|
|
2497
|
+
} else if (d.unified_diff) {
|
|
2498
|
+
html += renderUnifiedDiff(d.unified_diff);
|
|
2499
|
+
} else {
|
|
2500
|
+
html += '<div class="git-diff-loading" style="color:#666;">无变更内容</div>';
|
|
2501
|
+
}
|
|
2460
2502
|
|
|
2461
|
-
|
|
2462
|
-
|
|
2463
|
-
})
|
|
2464
|
-
.catch(function(err) {
|
|
2465
|
-
area.innerHTML = '<div class="git-diff-error">加载失败: ' + escapeHtml(err.message) + '</div>';
|
|
2466
|
-
});
|
|
2503
|
+
html += '</div>';
|
|
2504
|
+
area.innerHTML = html;
|
|
2467
2505
|
}
|
|
2468
2506
|
|
|
2469
2507
|
function renderUnifiedDiff(diffText) {
|
|
@@ -2496,13 +2534,13 @@
|
|
|
2496
2534
|
html += '<tr class="diff-line diff-line-add">';
|
|
2497
2535
|
html += '<td class="diff-line-num"></td>';
|
|
2498
2536
|
html += '<td class="diff-line-num">' + newLine + '</td>';
|
|
2499
|
-
html += '<td class="diff-line-content">' + escapeHtml(line.substring(1)) + '</td></tr>';
|
|
2537
|
+
html += '<td class="diff-line-content"><span class="diff-sign">+</span>' + escapeHtml(line.substring(1)) + '</td></tr>';
|
|
2500
2538
|
newLine++;
|
|
2501
2539
|
} else if (line.startsWith('-')) {
|
|
2502
2540
|
html += '<tr class="diff-line diff-line-del">';
|
|
2503
2541
|
html += '<td class="diff-line-num">' + oldLine + '</td>';
|
|
2504
2542
|
html += '<td class="diff-line-num"></td>';
|
|
2505
|
-
html += '<td class="diff-line-content">' + escapeHtml(line.substring(1)) + '</td></tr>';
|
|
2543
|
+
html += '<td class="diff-line-content"><span class="diff-sign">-</span>' + escapeHtml(line.substring(1)) + '</td></tr>';
|
|
2506
2544
|
oldLine++;
|
|
2507
2545
|
} else if (line.startsWith(' ') || (line === '' && i < lines.length - 1)) {
|
|
2508
2546
|
html += '<tr class="diff-line">';
|
|
@@ -2608,6 +2646,15 @@
|
|
|
2608
2646
|
});
|
|
2609
2647
|
}
|
|
2610
2648
|
|
|
2649
|
+
function formatDocContent(text) {
|
|
2650
|
+
if (!text) return '';
|
|
2651
|
+
try {
|
|
2652
|
+
return DOMPurify.sanitize(marked.parse(text, { breaks: true }));
|
|
2653
|
+
} catch (e) {
|
|
2654
|
+
return '<pre>' + escapeHtml(text) + '</pre>';
|
|
2655
|
+
}
|
|
2656
|
+
}
|
|
2657
|
+
|
|
2611
2658
|
function loadDocContent(file) {
|
|
2612
2659
|
var area = document.getElementById('docs-content-area');
|
|
2613
2660
|
area.innerHTML = '<div class="docs-loading">加载中...</div>';
|
|
@@ -2618,7 +2665,7 @@
|
|
|
2618
2665
|
area.innerHTML = '<div class="docs-loading" style="color:#ff6b6b;">' + escapeHtml(data.error) + '</div>';
|
|
2619
2666
|
return;
|
|
2620
2667
|
}
|
|
2621
|
-
area.innerHTML = '<
|
|
2668
|
+
area.innerHTML = '<div class="docs-md-content">' + formatDocContent(data.content) + '</div>';
|
|
2622
2669
|
})
|
|
2623
2670
|
.catch(function(err) {
|
|
2624
2671
|
area.innerHTML = '<div class="docs-loading" style="color:#ff6b6b;">加载失败</div>';
|
package/index.html
CHANGED
|
@@ -809,6 +809,39 @@
|
|
|
809
809
|
line-height: 1.6;
|
|
810
810
|
color: #d4d4d4;
|
|
811
811
|
}
|
|
812
|
+
.docs-md-content {
|
|
813
|
+
font-size: 13px; line-height: 1.7; color: #d4d4d4; word-break: break-word;
|
|
814
|
+
}
|
|
815
|
+
.docs-md-content h1, .docs-md-content h2, .docs-md-content h3 {
|
|
816
|
+
margin: 16px 0 8px; color: #e0e0e0; border-bottom: 1px solid #333; padding-bottom: 4px;
|
|
817
|
+
}
|
|
818
|
+
.docs-md-content h1 { font-size: 1.4em; }
|
|
819
|
+
.docs-md-content h2 { font-size: 1.2em; }
|
|
820
|
+
.docs-md-content h3 { font-size: 1.05em; }
|
|
821
|
+
.docs-md-content p { margin: 8px 0; }
|
|
822
|
+
.docs-md-content pre {
|
|
823
|
+
background: #1a1a1a; border: 1px solid #333; border-radius: 4px;
|
|
824
|
+
padding: 10px; overflow-x: auto; margin: 8px 0; white-space: pre-wrap; word-break: break-word;
|
|
825
|
+
}
|
|
826
|
+
.docs-md-content code {
|
|
827
|
+
background: #1a1a1a; padding: 1px 4px; border-radius: 3px; font-size: 12px;
|
|
828
|
+
font-family: Menlo, Monaco, monospace;
|
|
829
|
+
}
|
|
830
|
+
.docs-md-content pre code { background: none; padding: 0; }
|
|
831
|
+
.docs-md-content ul, .docs-md-content ol { margin: 8px 0; padding-left: 20px; }
|
|
832
|
+
.docs-md-content li { margin: 4px 0; }
|
|
833
|
+
.docs-md-content blockquote {
|
|
834
|
+
border-left: 3px solid #444; margin: 8px 0; padding: 4px 12px; color: #999;
|
|
835
|
+
}
|
|
836
|
+
.docs-md-content table { border-collapse: collapse; margin: 8px 0; width: 100%; }
|
|
837
|
+
.docs-md-content th, .docs-md-content td {
|
|
838
|
+
border: 1px solid #333; padding: 6px 10px; text-align: left;
|
|
839
|
+
}
|
|
840
|
+
.docs-md-content th { background: #1a1a1a; }
|
|
841
|
+
.docs-md-content a { color: #58a6ff; text-decoration: none; }
|
|
842
|
+
.docs-md-content a:hover { text-decoration: underline; }
|
|
843
|
+
.docs-md-content img { max-width: 100%; }
|
|
844
|
+
.docs-md-content hr { border: none; border-top: 1px solid #333; margin: 12px 0; }
|
|
812
845
|
.docs-placeholder {
|
|
813
846
|
flex: 1;
|
|
814
847
|
display: flex;
|
|
@@ -874,6 +907,13 @@
|
|
|
874
907
|
color: #ffa198;
|
|
875
908
|
}
|
|
876
909
|
|
|
910
|
+
.diff-sign {
|
|
911
|
+
display: inline-block;
|
|
912
|
+
width: 12px;
|
|
913
|
+
font-weight: bold;
|
|
914
|
+
user-select: none;
|
|
915
|
+
}
|
|
916
|
+
|
|
877
917
|
.diff-line-hunk {
|
|
878
918
|
background: rgba(56, 139, 253, 0.1);
|
|
879
919
|
}
|
|
@@ -2633,38 +2673,29 @@
|
|
|
2633
2673
|
|
|
2634
2674
|
function loadDiffContent(file) {
|
|
2635
2675
|
var area = document.getElementById('git-diff-content-area');
|
|
2636
|
-
|
|
2637
|
-
|
|
2638
|
-
|
|
2639
|
-
|
|
2640
|
-
|
|
2641
|
-
|
|
2642
|
-
|
|
2643
|
-
|
|
2644
|
-
|
|
2645
|
-
|
|
2646
|
-
|
|
2647
|
-
|
|
2648
|
-
|
|
2649
|
-
|
|
2650
|
-
|
|
2651
|
-
|
|
2652
|
-
|
|
2653
|
-
|
|
2654
|
-
|
|
2655
|
-
|
|
2656
|
-
} else if (d.unified_diff) {
|
|
2657
|
-
html += renderUnifiedDiff(d.unified_diff);
|
|
2658
|
-
} else {
|
|
2659
|
-
html += '<div class="git-diff-loading" style="color:#666;">无变更内容</div>';
|
|
2660
|
-
}
|
|
2676
|
+
var d = diffChanges.find(function(c) { return c.file === file; });
|
|
2677
|
+
if (!d) {
|
|
2678
|
+
area.innerHTML = '<div class="git-diff-error">无 diff 数据</div>';
|
|
2679
|
+
return;
|
|
2680
|
+
}
|
|
2681
|
+
var html = '<div class="git-diff-content-header">';
|
|
2682
|
+
html += '<span class="git-diff-content-path">' + escapeHtml(d.file) + '</span>';
|
|
2683
|
+
html += '<span class="git-diff-badge">' + (d.is_new ? 'NEW' : d.is_deleted ? 'DEL' : 'DIFF') + '</span>';
|
|
2684
|
+
html += '</div>';
|
|
2685
|
+
html += '<div class="git-diff-content-scroll">';
|
|
2686
|
+
|
|
2687
|
+
if (d.is_binary) {
|
|
2688
|
+
html += '<div class="git-diff-loading" style="font-style:italic;">二进制文件</div>';
|
|
2689
|
+
} else if (d.is_large) {
|
|
2690
|
+
html += '<div class="git-diff-loading" style="color:#e2c08d;">文件过大: ' + (d.size / (1024 * 1024)).toFixed(2) + ' MB</div>';
|
|
2691
|
+
} else if (d.unified_diff) {
|
|
2692
|
+
html += renderUnifiedDiff(d.unified_diff);
|
|
2693
|
+
} else {
|
|
2694
|
+
html += '<div class="git-diff-loading" style="color:#666;">无变更内容</div>';
|
|
2695
|
+
}
|
|
2661
2696
|
|
|
2662
|
-
|
|
2663
|
-
|
|
2664
|
-
})
|
|
2665
|
-
.catch(function(err) {
|
|
2666
|
-
area.innerHTML = '<div class="git-diff-error">加载失败: ' + escapeHtml(err.message) + '</div>';
|
|
2667
|
-
});
|
|
2697
|
+
html += '</div>';
|
|
2698
|
+
area.innerHTML = html;
|
|
2668
2699
|
}
|
|
2669
2700
|
|
|
2670
2701
|
function renderUnifiedDiff(diffText) {
|
|
@@ -2697,13 +2728,13 @@
|
|
|
2697
2728
|
html += '<tr class="diff-line diff-line-add">';
|
|
2698
2729
|
html += '<td class="diff-line-num"></td>';
|
|
2699
2730
|
html += '<td class="diff-line-num">' + newLine + '</td>';
|
|
2700
|
-
html += '<td class="diff-line-content">' + escapeHtml(line.substring(1)) + '</td></tr>';
|
|
2731
|
+
html += '<td class="diff-line-content"><span class="diff-sign">+</span>' + escapeHtml(line.substring(1)) + '</td></tr>';
|
|
2701
2732
|
newLine++;
|
|
2702
2733
|
} else if (line.startsWith('-')) {
|
|
2703
2734
|
html += '<tr class="diff-line diff-line-del">';
|
|
2704
2735
|
html += '<td class="diff-line-num">' + oldLine + '</td>';
|
|
2705
2736
|
html += '<td class="diff-line-num"></td>';
|
|
2706
|
-
html += '<td class="diff-line-content">' + escapeHtml(line.substring(1)) + '</td></tr>';
|
|
2737
|
+
html += '<td class="diff-line-content"><span class="diff-sign">-</span>' + escapeHtml(line.substring(1)) + '</td></tr>';
|
|
2707
2738
|
oldLine++;
|
|
2708
2739
|
} else if (line.startsWith(' ') || (line === '' && i < lines.length - 1)) {
|
|
2709
2740
|
html += '<tr class="diff-line">';
|
|
@@ -2811,6 +2842,15 @@
|
|
|
2811
2842
|
});
|
|
2812
2843
|
}
|
|
2813
2844
|
|
|
2845
|
+
function formatDocContent(text) {
|
|
2846
|
+
if (!text) return '';
|
|
2847
|
+
try {
|
|
2848
|
+
return DOMPurify.sanitize(marked.parse(text, { breaks: true }));
|
|
2849
|
+
} catch (e) {
|
|
2850
|
+
return '<pre>' + escapeHtml(text) + '</pre>';
|
|
2851
|
+
}
|
|
2852
|
+
}
|
|
2853
|
+
|
|
2814
2854
|
function loadDocContent(file) {
|
|
2815
2855
|
var area = document.getElementById('docs-content-area');
|
|
2816
2856
|
area.innerHTML = '<div class="docs-loading">加载中...</div>';
|
|
@@ -2821,7 +2861,7 @@
|
|
|
2821
2861
|
area.innerHTML = '<div class="docs-loading" style="color:#ff6b6b;">' + escapeHtml(data.error) + '</div>';
|
|
2822
2862
|
return;
|
|
2823
2863
|
}
|
|
2824
|
-
area.innerHTML = '<
|
|
2864
|
+
area.innerHTML = '<div class="docs-md-content">' + formatDocContent(data.content) + '</div>';
|
|
2825
2865
|
})
|
|
2826
2866
|
.catch(function() {
|
|
2827
2867
|
area.innerHTML = '<div class="docs-loading" style="color:#ff6b6b;">加载失败</div>';
|
package/package.json
CHANGED
package/server.js
CHANGED
|
@@ -655,7 +655,7 @@ const requestHandler = async (req, res) => {
|
|
|
655
655
|
return;
|
|
656
656
|
}
|
|
657
657
|
|
|
658
|
-
// API: 获取 git status
|
|
658
|
+
// API: 获取 git status(含每个文件的 unified_diff,批量获取优化)
|
|
659
659
|
if (req.url === '/api/git-status') {
|
|
660
660
|
res.writeHead(200, {
|
|
661
661
|
'Content-Type': 'application/json',
|
|
@@ -663,13 +663,76 @@ const requestHandler = async (req, res) => {
|
|
|
663
663
|
});
|
|
664
664
|
try {
|
|
665
665
|
const gitCwd = process.env.PROJECT_DIR || process.cwd();
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
666
|
+
|
|
667
|
+
// 并行执行: git status + git diff --numstat + git diff (批量获取)
|
|
668
|
+
const [statusResult, numstatResult, diffResult] = await Promise.all([
|
|
669
|
+
execFileAsync('git', ['-c', 'safe.directory=*', 'status', '--porcelain'], { cwd: gitCwd, encoding: 'utf-8', timeout: 60000 }),
|
|
670
|
+
execFileAsync('git', ['-c', 'safe.directory=*', 'diff', '--numstat', 'HEAD'], { cwd: gitCwd, encoding: 'utf-8', timeout: 60000 }).catch(() => ({ stdout: '' })),
|
|
671
|
+
execFileAsync('git', ['-c', 'safe.directory=*', 'diff', '--no-color', '-U3', 'HEAD'], { cwd: gitCwd, encoding: 'utf-8', timeout: 60000, maxBuffer: 10 * 1024 * 1024 }).catch(e => ({ stdout: e.stdout || '' })),
|
|
672
|
+
]);
|
|
673
|
+
|
|
674
|
+
const changes = statusResult.stdout.split('\n').filter(Boolean).map(line => ({
|
|
670
675
|
status: line.substring(0, 2).trim(),
|
|
671
676
|
file: line.substring(3),
|
|
672
677
|
})).filter(c => !/^core-/.test(c.file));
|
|
678
|
+
|
|
679
|
+
// 解析 numstat 识别二进制文件
|
|
680
|
+
const binaryFiles = new Set();
|
|
681
|
+
numstatResult.stdout.split('\n').filter(Boolean).forEach(line => {
|
|
682
|
+
if (line.startsWith('-\t-\t')) binaryFiles.add(line.split('\t')[2]);
|
|
683
|
+
});
|
|
684
|
+
|
|
685
|
+
// 将批量 diff 输出按文件拆分
|
|
686
|
+
const diffMap = {};
|
|
687
|
+
const diffParts = diffResult.stdout.split(/^diff --git /m);
|
|
688
|
+
for (let i = 1; i < diffParts.length; i++) {
|
|
689
|
+
const part = diffParts[i];
|
|
690
|
+
// 提取文件名: "a/path b/path\n..."
|
|
691
|
+
const firstLine = part.substring(0, part.indexOf('\n'));
|
|
692
|
+
const bMatch = firstLine.match(/ b\/(.+)$/);
|
|
693
|
+
if (bMatch) diffMap[bMatch[1]] = 'diff --git ' + part;
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
// 填充每个文件的 diff 信息
|
|
697
|
+
const untrackedFiles = [];
|
|
698
|
+
for (const c of changes) {
|
|
699
|
+
if (c.file.includes('..') || c.file.startsWith('/')) continue;
|
|
700
|
+
c.is_new = c.status === 'A' || c.status === '??';
|
|
701
|
+
c.is_deleted = c.status === 'D';
|
|
702
|
+
c.is_binary = binaryFiles.has(c.file);
|
|
703
|
+
if (c.is_binary) continue;
|
|
704
|
+
|
|
705
|
+
// 检查大文件
|
|
706
|
+
if (!c.is_deleted) {
|
|
707
|
+
try {
|
|
708
|
+
const filePath = join(gitCwd, c.file);
|
|
709
|
+
if (existsSync(filePath)) {
|
|
710
|
+
const stat = statSync(filePath);
|
|
711
|
+
if (stat.size > 5 * 1024 * 1024) { c.is_large = true; c.size = stat.size; continue; }
|
|
712
|
+
}
|
|
713
|
+
} catch {}
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
if (c.status === '??') {
|
|
717
|
+
// untracked 文件需要单独处理
|
|
718
|
+
untrackedFiles.push(c);
|
|
719
|
+
} else {
|
|
720
|
+
c.unified_diff = diffMap[c.file] || '';
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
// 对 untracked 文件并行获取 diff
|
|
725
|
+
if (untrackedFiles.length > 0) {
|
|
726
|
+
await Promise.all(untrackedFiles.map(async (c) => {
|
|
727
|
+
try {
|
|
728
|
+
const { stdout: diffOut } = await execFileAsync('git', ['-c', 'safe.directory=*', 'diff', '--no-color', '-U3', '--no-index', '/dev/null', c.file], { cwd: gitCwd, encoding: 'utf-8', timeout: 30000, maxBuffer: 5 * 1024 * 1024 });
|
|
729
|
+
c.unified_diff = diffOut;
|
|
730
|
+
} catch (e) {
|
|
731
|
+
c.unified_diff = e.stdout || '';
|
|
732
|
+
}
|
|
733
|
+
}));
|
|
734
|
+
}
|
|
735
|
+
|
|
673
736
|
res.end(JSON.stringify({ changes, cwd: gitCwd }));
|
|
674
737
|
} catch (err) {
|
|
675
738
|
res.end(JSON.stringify({ changes: [], cwd: process.env.PROJECT_DIR || process.cwd(), error: err.message }));
|
|
@@ -1119,8 +1182,16 @@ wssInst.on('connection', (ws, req) => {
|
|
|
1119
1182
|
}
|
|
1120
1183
|
isSwitching = false;
|
|
1121
1184
|
}, 200);
|
|
1122
|
-
} else if (
|
|
1123
|
-
|
|
1185
|
+
} else if (currentProcess) {
|
|
1186
|
+
// TUI 程序使用 alternate screen buffer,直接回放 raw buffer 容易转义序列错乱。
|
|
1187
|
+
// 改为发送 resize 信号让 TUI 重绘当前画面。
|
|
1188
|
+
// 先改为不同尺寸再改回,强制触发 SIGWINCH(相同尺寸不触发)。
|
|
1189
|
+
try {
|
|
1190
|
+
currentProcess.resize(Math.max(2, lastPtyCols - 1), lastPtyRows);
|
|
1191
|
+
setTimeout(() => {
|
|
1192
|
+
try { currentProcess.resize(lastPtyCols, lastPtyRows); } catch {}
|
|
1193
|
+
}, 50);
|
|
1194
|
+
} catch {}
|
|
1124
1195
|
}
|
|
1125
1196
|
|
|
1126
1197
|
|
package/test-doc.md
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
# Claude OpenCode Viewer 使用指南
|
|
2
|
+
|
|
3
|
+
## 简介
|
|
4
|
+
|
|
5
|
+
Claude OpenCode Viewer(COV)是一个统一的终端查看器,支持在浏览器中远程查看和操作 Claude Code 与 OpenCode 的终端会话。适用于需要在手机或其他设备上查看 AI 编程助手工作进度的场景。
|
|
6
|
+
|
|
7
|
+
## 安装
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install -g claude-opencode-viewer
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
安装完成后,可以通过以下命令启动:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
cov
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
默认监听端口 7008,PC 模式使用 `--pc` 参数启动。
|
|
20
|
+
|
|
21
|
+
## 功能特性
|
|
22
|
+
|
|
23
|
+
### 终端查看
|
|
24
|
+
|
|
25
|
+
支持实时查看终端输出,基于 xterm.js 实现完整的终端模拟,包括:
|
|
26
|
+
|
|
27
|
+
- 颜色渲染
|
|
28
|
+
- Unicode 字符支持
|
|
29
|
+
- WebGL 加速渲染
|
|
30
|
+
- 移动端触摸滚动
|
|
31
|
+
|
|
32
|
+
### 会话管理
|
|
33
|
+
|
|
34
|
+
可以管理多个会话,支持以下操作:
|
|
35
|
+
|
|
36
|
+
1. 查看历史会话列表
|
|
37
|
+
2. 恢复已有会话
|
|
38
|
+
3. 创建新会话
|
|
39
|
+
4. 在 Claude 和 OpenCode 之间切换
|
|
40
|
+
|
|
41
|
+
### Git 变更查看
|
|
42
|
+
|
|
43
|
+
集成了 Git 状态查看功能,可以直接在页面上查看:
|
|
44
|
+
|
|
45
|
+
| 状态 | 含义 | 颜色 |
|
|
46
|
+
|------|------|------|
|
|
47
|
+
| M | 已修改 | 橙色 |
|
|
48
|
+
| A | 新增 | 绿色 |
|
|
49
|
+
| D | 已删除 | 红色 |
|
|
50
|
+
| ?? | 未跟踪 | 灰色 |
|
|
51
|
+
|
|
52
|
+
### 文档浏览
|
|
53
|
+
|
|
54
|
+
支持浏览项目中的 Markdown 文档,自动扫描项目目录下的 `.md` 文件并以富文本格式展示。
|
|
55
|
+
|
|
56
|
+
## 配置说明
|
|
57
|
+
|
|
58
|
+
> 注意:以下配置需要在项目根目录下操作,确保 `PROJECT_DIR` 环境变量指向正确的项目路径。
|
|
59
|
+
|
|
60
|
+
常用环境变量:
|
|
61
|
+
|
|
62
|
+
- `PROJECT_DIR` — 指定项目工作目录
|
|
63
|
+
- `PORT` — 自定义端口号
|
|
64
|
+
- `COV_MODE` — 默认启动模式(claude / opencode)
|
|
65
|
+
|
|
66
|
+
## 常见问题
|
|
67
|
+
|
|
68
|
+
### 连接断开怎么办?
|
|
69
|
+
|
|
70
|
+
页面会自动显示重连提示并尝试重新连接。如果持续无法连接,请检查:
|
|
71
|
+
|
|
72
|
+
1. 服务进程是否仍在运行
|
|
73
|
+
2. 网络是否可达
|
|
74
|
+
3. 端口是否被占用
|
|
75
|
+
|
|
76
|
+
### 移动端键盘遮挡问题
|
|
77
|
+
|
|
78
|
+
在 iOS 设备上,系统会自动调整终端高度以适应键盘弹出。如果遇到显示异常,可以尝试旋转屏幕后再旋转回来。
|
|
79
|
+
|
|
80
|
+
## 更新日志
|
|
81
|
+
|
|
82
|
+
**v2.6.48** — 修复重连内容重复、模式切换黑屏问题
|
|
83
|
+
|
|
84
|
+
**v2.6.47** — 添加 PC 端重连覆盖层
|
|
85
|
+
|
|
86
|
+
**v2.6.46** — 移动端键盘交互优化
|