claude-opencode-viewer 2.6.10 → 2.6.12
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 +31 -422
- package/index.html +30 -344
- package/package.json +1 -1
- package/server.js +2 -2
package/index-pc.html
CHANGED
|
@@ -87,170 +87,6 @@
|
|
|
87
87
|
-webkit-overflow-scrolling: touch;
|
|
88
88
|
}
|
|
89
89
|
|
|
90
|
-
#session-detail-view {
|
|
91
|
-
display: none;
|
|
92
|
-
flex-direction: column;
|
|
93
|
-
flex: 1;
|
|
94
|
-
min-height: 0;
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
#session-detail-view.visible {
|
|
98
|
-
display: flex;
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
#session-detail-header {
|
|
102
|
-
padding: 12px 16px;
|
|
103
|
-
background: #0d0d0d;
|
|
104
|
-
border-bottom: 1px solid #222;
|
|
105
|
-
flex-shrink: 0;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
#session-detail-title {
|
|
109
|
-
font-size: 14px;
|
|
110
|
-
color: #ddd;
|
|
111
|
-
font-weight: 500;
|
|
112
|
-
margin-bottom: 4px;
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
#session-detail-meta {
|
|
116
|
-
font-size: 11px;
|
|
117
|
-
color: #888;
|
|
118
|
-
display: flex;
|
|
119
|
-
gap: 12px;
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
#session-detail-content {
|
|
123
|
-
flex: 1;
|
|
124
|
-
overflow-y: auto;
|
|
125
|
-
padding: 16px;
|
|
126
|
-
-webkit-overflow-scrolling: touch;
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
.message-item {
|
|
130
|
-
margin-bottom: 20px;
|
|
131
|
-
display: flex;
|
|
132
|
-
gap: 12px;
|
|
133
|
-
align-items: flex-start;
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
/* User 消息:左对齐 */
|
|
137
|
-
.message-user {
|
|
138
|
-
justify-content: flex-start;
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
/* Assistant 消息:右对齐 */
|
|
142
|
-
.message-assistant {
|
|
143
|
-
justify-content: flex-end;
|
|
144
|
-
flex-direction: row-reverse;
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
.message-avatar {
|
|
148
|
-
flex-shrink: 0;
|
|
149
|
-
width: 32px;
|
|
150
|
-
height: 32px;
|
|
151
|
-
border-radius: 50%;
|
|
152
|
-
background: #2a4a7c;
|
|
153
|
-
display: flex;
|
|
154
|
-
align-items: center;
|
|
155
|
-
justify-content: center;
|
|
156
|
-
font-size: 14px;
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
.message-assistant .message-avatar {
|
|
160
|
-
background: #1a5a3a;
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
.message-content {
|
|
164
|
-
flex: 1;
|
|
165
|
-
min-width: 0;
|
|
166
|
-
max-width: 80%;
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
.message-header {
|
|
170
|
-
display: flex;
|
|
171
|
-
align-items: center;
|
|
172
|
-
gap: 8px;
|
|
173
|
-
margin-bottom: 6px;
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
/* User 的标题左对齐 */
|
|
177
|
-
.message-user .message-header {
|
|
178
|
-
justify-content: flex-start;
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
/* Assistant 的标题右对齐 */
|
|
182
|
-
.message-assistant .message-header {
|
|
183
|
-
justify-content: flex-end;
|
|
184
|
-
flex-direction: row-reverse;
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
.message-role {
|
|
188
|
-
font-size: 11px;
|
|
189
|
-
color: #888;
|
|
190
|
-
font-weight: 600;
|
|
191
|
-
text-transform: uppercase;
|
|
192
|
-
letter-spacing: 0.5px;
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
.message-text {
|
|
196
|
-
color: #ddd;
|
|
197
|
-
font-size: 13px;
|
|
198
|
-
line-height: 1.6;
|
|
199
|
-
white-space: pre-wrap;
|
|
200
|
-
word-break: break-word;
|
|
201
|
-
background: #141414;
|
|
202
|
-
padding: 12px;
|
|
203
|
-
border-radius: 8px;
|
|
204
|
-
border: 1px solid #222;
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
/* User 消息气泡 */
|
|
208
|
-
.message-user .message-text {
|
|
209
|
-
background: #1a2332;
|
|
210
|
-
border-color: #2a4a7c;
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
/* Assistant 消息气泡 */
|
|
214
|
-
.message-assistant .message-text {
|
|
215
|
-
background: #1a2e1a;
|
|
216
|
-
border-color: #2a5a3a;
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
.message-tool-call {
|
|
220
|
-
margin-top: 8px;
|
|
221
|
-
padding: 8px 12px;
|
|
222
|
-
background: #1a1a0a;
|
|
223
|
-
border: 1px solid #333;
|
|
224
|
-
border-radius: 6px;
|
|
225
|
-
font-size: 12px;
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
.message-tool-name {
|
|
229
|
-
color: #f0ad4e;
|
|
230
|
-
font-weight: 600;
|
|
231
|
-
display: flex;
|
|
232
|
-
align-items: center;
|
|
233
|
-
gap: 6px;
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
.message-tool-result {
|
|
237
|
-
margin-top: 6px;
|
|
238
|
-
padding: 8px;
|
|
239
|
-
background: #0a0a0a;
|
|
240
|
-
border-radius: 4px;
|
|
241
|
-
font-size: 11px;
|
|
242
|
-
color: #999;
|
|
243
|
-
max-height: 100px;
|
|
244
|
-
overflow-y: auto;
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
.message-empty {
|
|
248
|
-
text-align: center;
|
|
249
|
-
padding: 40px 20px;
|
|
250
|
-
color: #666;
|
|
251
|
-
font-size: 13px;
|
|
252
|
-
}
|
|
253
|
-
|
|
254
90
|
#session-list {
|
|
255
91
|
display: flex;
|
|
256
92
|
flex-direction: column;
|
|
@@ -559,58 +395,6 @@
|
|
|
559
395
|
display: block;
|
|
560
396
|
}
|
|
561
397
|
|
|
562
|
-
#copy-popup {
|
|
563
|
-
display: none;
|
|
564
|
-
position: fixed;
|
|
565
|
-
top: 50%;
|
|
566
|
-
left: 50%;
|
|
567
|
-
transform: translate(-50%, -50%);
|
|
568
|
-
background: #1a1a1a;
|
|
569
|
-
border: 1px solid #444;
|
|
570
|
-
border-radius: 8px;
|
|
571
|
-
padding: 16px;
|
|
572
|
-
z-index: 10000;
|
|
573
|
-
max-width: 80vw;
|
|
574
|
-
max-height: 60vh;
|
|
575
|
-
}
|
|
576
|
-
#copy-popup.show {
|
|
577
|
-
display: flex;
|
|
578
|
-
flex-direction: column;
|
|
579
|
-
gap: 10px;
|
|
580
|
-
}
|
|
581
|
-
#copy-popup textarea {
|
|
582
|
-
width: 500px;
|
|
583
|
-
max-width: 70vw;
|
|
584
|
-
height: 120px;
|
|
585
|
-
background: #0a0a0a;
|
|
586
|
-
color: #ccc;
|
|
587
|
-
border: 1px solid #333;
|
|
588
|
-
border-radius: 4px;
|
|
589
|
-
padding: 8px;
|
|
590
|
-
font-family: Menlo, Monaco, monospace;
|
|
591
|
-
font-size: 12px;
|
|
592
|
-
resize: vertical;
|
|
593
|
-
}
|
|
594
|
-
#copy-popup .popup-actions {
|
|
595
|
-
display: flex;
|
|
596
|
-
justify-content: flex-end;
|
|
597
|
-
gap: 8px;
|
|
598
|
-
}
|
|
599
|
-
#copy-popup button {
|
|
600
|
-
padding: 6px 16px;
|
|
601
|
-
border: none;
|
|
602
|
-
border-radius: 4px;
|
|
603
|
-
cursor: pointer;
|
|
604
|
-
font-size: 13px;
|
|
605
|
-
}
|
|
606
|
-
#copy-popup .btn-copy {
|
|
607
|
-
background: #2563eb;
|
|
608
|
-
color: #fff;
|
|
609
|
-
}
|
|
610
|
-
#copy-popup .btn-close {
|
|
611
|
-
background: #333;
|
|
612
|
-
color: #ccc;
|
|
613
|
-
}
|
|
614
398
|
|
|
615
399
|
/* Git Diff 面板 */
|
|
616
400
|
#git-diff-bar {
|
|
@@ -894,38 +678,6 @@
|
|
|
894
678
|
</div>
|
|
895
679
|
</div>
|
|
896
680
|
|
|
897
|
-
<!-- 会话详情视图 -->
|
|
898
|
-
<div id="session-detail-view">
|
|
899
|
-
<div id="session-history-header">
|
|
900
|
-
<div id="session-history-title">会话详情</div>
|
|
901
|
-
<div id="session-history-actions">
|
|
902
|
-
<button class="history-toggle-btn" id="restore-session" style="background: #1a5a3a; border-color: #2a7a4a;">
|
|
903
|
-
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
904
|
-
<polyline points="1 4 1 10 7 10"></polyline>
|
|
905
|
-
<path d="M3.51 15a9 9 0 1 0 2.13-9.36L1 10"></path>
|
|
906
|
-
</svg>
|
|
907
|
-
<span>恢复会话</span>
|
|
908
|
-
</button>
|
|
909
|
-
<button class="history-toggle-btn" id="back-to-list">
|
|
910
|
-
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
911
|
-
<line x1="19" y1="12" x2="5" y2="12"></line>
|
|
912
|
-
<polyline points="12 19 5 12 12 5"></polyline>
|
|
913
|
-
</svg>
|
|
914
|
-
<span>返回列表</span>
|
|
915
|
-
</button>
|
|
916
|
-
</div>
|
|
917
|
-
</div>
|
|
918
|
-
<div id="session-detail-header">
|
|
919
|
-
<div id="session-detail-title">会话标题</div>
|
|
920
|
-
<div id="session-detail-meta">
|
|
921
|
-
<span id="session-detail-time">时间</span>
|
|
922
|
-
<span id="session-detail-dir">目录</span>
|
|
923
|
-
</div>
|
|
924
|
-
</div>
|
|
925
|
-
<div id="session-detail-content">
|
|
926
|
-
<div class="message-empty">加载中...</div>
|
|
927
|
-
</div>
|
|
928
|
-
</div>
|
|
929
681
|
</div>
|
|
930
682
|
|
|
931
683
|
<!-- Git Diff 面板 -->
|
|
@@ -982,14 +734,6 @@
|
|
|
982
734
|
</div>
|
|
983
735
|
|
|
984
736
|
<div id="copy-toast">已复制</div>
|
|
985
|
-
<div id="copy-popup">
|
|
986
|
-
<div style="color:#ccc;font-size:13px;">剪贴板写入需要 HTTPS,请手动复制:</div>
|
|
987
|
-
<textarea id="copy-popup-text" readonly></textarea>
|
|
988
|
-
<div class="popup-actions">
|
|
989
|
-
<button class="btn-copy" onclick="document.getElementById('copy-popup-text').select();document.execCommand('copy');document.getElementById('copy-popup').classList.remove('show');">选中并复制</button>
|
|
990
|
-
<button class="btn-close" onclick="document.getElementById('copy-popup').classList.remove('show');">关闭</button>
|
|
991
|
-
</div>
|
|
992
|
-
</div>
|
|
993
737
|
<script src="https://cdn.jsdelivr.net/npm/@xterm/xterm@5.5.0/lib/xterm.min.js"></script>
|
|
994
738
|
<script>
|
|
995
739
|
(function() {
|
|
@@ -1038,19 +782,15 @@
|
|
|
1038
782
|
try {
|
|
1039
783
|
var bytes = Uint8Array.from(atob(b64), function(c) { return c.charCodeAt(0); });
|
|
1040
784
|
var text = new TextDecoder().decode(bytes);
|
|
1041
|
-
|
|
1042
|
-
navigator.clipboard.writeText(text).then(function() {
|
|
1043
|
-
showCopyToast();
|
|
1044
|
-
}).catch(function() {
|
|
1045
|
-
showCopyPopup(text);
|
|
1046
|
-
});
|
|
1047
|
-
} else {
|
|
1048
|
-
showCopyPopup(text);
|
|
1049
|
-
}
|
|
785
|
+
copyToClipboard(text);
|
|
1050
786
|
} catch (e) {}
|
|
1051
787
|
return true;
|
|
1052
788
|
});
|
|
1053
789
|
|
|
790
|
+
// 自动检测反向代理子路径,确保 API/WS 请求带正确前缀
|
|
791
|
+
var basePath = location.pathname.replace(/\/[^/]*$/, '');
|
|
792
|
+
if (basePath === '' || basePath === '/') basePath = '';
|
|
793
|
+
|
|
1054
794
|
var modeSelect = document.getElementById('mode-select');
|
|
1055
795
|
var terminalEl = document.getElementById('terminal');
|
|
1056
796
|
var ws = null;
|
|
@@ -1466,7 +1206,7 @@
|
|
|
1466
1206
|
|
|
1467
1207
|
function connect() {
|
|
1468
1208
|
var proto = location.protocol === 'https:' ? 'wss:' : 'ws:';
|
|
1469
|
-
ws = new WebSocket(proto + '//' + location.host + '/ws');
|
|
1209
|
+
ws = new WebSocket(proto + '//' + location.host + basePath + '/ws');
|
|
1470
1210
|
|
|
1471
1211
|
ws.onopen = function() {
|
|
1472
1212
|
isBufferReplay = true;
|
|
@@ -1612,7 +1352,7 @@
|
|
|
1612
1352
|
var sessionList = document.getElementById('session-list');
|
|
1613
1353
|
sessionList.innerHTML = '<div class="session-loading">加载历史会话...</div>';
|
|
1614
1354
|
|
|
1615
|
-
fetch('/api/sessions')
|
|
1355
|
+
fetch(basePath + '/api/sessions')
|
|
1616
1356
|
.then(function(response) { return response.json(); })
|
|
1617
1357
|
.then(function(data) {
|
|
1618
1358
|
sessions = data;
|
|
@@ -1695,7 +1435,7 @@
|
|
|
1695
1435
|
itemEl.style.opacity = '0.4';
|
|
1696
1436
|
itemEl.style.pointerEvents = 'none';
|
|
1697
1437
|
|
|
1698
|
-
fetch('/api/session/' + sessionId, { method: 'DELETE' })
|
|
1438
|
+
fetch(basePath + '/api/session/' + sessionId, { method: 'DELETE' })
|
|
1699
1439
|
.then(function(r) { return r.json(); })
|
|
1700
1440
|
.then(function(data) {
|
|
1701
1441
|
if (data.ok) {
|
|
@@ -1722,110 +1462,35 @@
|
|
|
1722
1462
|
});
|
|
1723
1463
|
}
|
|
1724
1464
|
|
|
1725
|
-
function showSessionDetail() {
|
|
1726
|
-
document.getElementById('session-list-view').classList.add('hidden');
|
|
1727
|
-
document.getElementById('session-detail-view').classList.add('visible');
|
|
1728
|
-
}
|
|
1729
1465
|
|
|
1730
|
-
function showSessionList() {
|
|
1731
|
-
document.getElementById('session-list-view').classList.remove('hidden');
|
|
1732
|
-
document.getElementById('session-detail-view').classList.remove('visible');
|
|
1733
|
-
}
|
|
1734
1466
|
|
|
1735
1467
|
function loadSession(session) {
|
|
1736
|
-
console.log('[
|
|
1737
|
-
|
|
1738
|
-
// 保存当前会话数据
|
|
1468
|
+
console.log('[restore] 直接恢复会话:', session.title);
|
|
1739
1469
|
currentSessionData = session;
|
|
1740
1470
|
|
|
1741
|
-
//
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
// 更新详情页标题和元信息
|
|
1745
|
-
document.getElementById('session-detail-title').textContent = session.preview || session.title;
|
|
1746
|
-
document.getElementById('session-detail-time').textContent = formatTime(session.time_updated);
|
|
1747
|
-
document.getElementById('session-detail-dir').textContent = session.directory.replace(/^\/Users\/[^\/]+/, '~');
|
|
1748
|
-
|
|
1749
|
-
var contentDiv = document.getElementById('session-detail-content');
|
|
1750
|
-
contentDiv.innerHTML = '<div class="message-empty">加载消息中...</div>';
|
|
1751
|
-
|
|
1752
|
-
// 获取会话的所有消息
|
|
1753
|
-
fetch('/api/session/' + session.id)
|
|
1754
|
-
.then(function(response) { return response.json(); })
|
|
1755
|
-
.then(function(messages) {
|
|
1756
|
-
console.log('[session] 收到', messages.length, '条消息');
|
|
1757
|
-
|
|
1758
|
-
if (messages.length === 0) {
|
|
1759
|
-
contentDiv.innerHTML = '<div class="message-empty">该会话暂无消息</div>';
|
|
1760
|
-
return;
|
|
1761
|
-
}
|
|
1762
|
-
|
|
1763
|
-
// 渲染消息列表
|
|
1764
|
-
var html = '';
|
|
1765
|
-
messages.forEach(function(msg) {
|
|
1766
|
-
console.log('[render] 消息:', msg);
|
|
1767
|
-
|
|
1768
|
-
var roleClass = msg.role === 'assistant' ? 'message-assistant' : 'message-user';
|
|
1769
|
-
var avatar = msg.role === 'user' ? '👤' : '🤖';
|
|
1770
|
-
var roleName = msg.role === 'user' ? 'User' : 'Assistant';
|
|
1771
|
-
|
|
1772
|
-
html += '<div class="message-item ' + roleClass + '">';
|
|
1773
|
-
html += '<div class="message-avatar">' + avatar + '</div>';
|
|
1774
|
-
html += '<div class="message-content">';
|
|
1775
|
-
html += '<div class="message-header">';
|
|
1776
|
-
html += '<div class="message-role">' + roleName + '</div>';
|
|
1777
|
-
html += '</div>';
|
|
1778
|
-
|
|
1779
|
-
// 显示推理过程(仅 assistant)
|
|
1780
|
-
if (msg.reasoning) {
|
|
1781
|
-
html += '<div class="message-text" style="background: #1a1a0a; border-color: #333;">';
|
|
1782
|
-
html += '<div style="color: #f0ad4e; font-size: 11px; font-weight: 600; margin-bottom: 6px;">💭 思考过程</div>';
|
|
1783
|
-
html += '<div style="color: #bbb;">' + escapeHtml(msg.reasoning) + '</div>';
|
|
1784
|
-
html += '</div>';
|
|
1785
|
-
}
|
|
1786
|
-
|
|
1787
|
-
// 显示文本内容
|
|
1788
|
-
if (msg.text) {
|
|
1789
|
-
html += '<div class="message-text">' + escapeHtml(msg.text) + '</div>';
|
|
1790
|
-
} else if (!msg.reasoning && !msg.toolCalls && !msg.toolResults) {
|
|
1791
|
-
html += '<div class="message-text" style="color: #666; font-style: italic;">(无文本内容)</div>';
|
|
1792
|
-
}
|
|
1793
|
-
|
|
1794
|
-
// 显示工具调用
|
|
1795
|
-
if (msg.toolCalls && msg.toolCalls.length > 0) {
|
|
1796
|
-
msg.toolCalls.forEach(function(tool) {
|
|
1797
|
-
html += '<div class="message-tool-call">';
|
|
1798
|
-
html += '<div class="message-tool-name">🔧 工具调用: ' + escapeHtml(tool.name || '未知工具') + '</div>';
|
|
1799
|
-
html += '</div>';
|
|
1800
|
-
});
|
|
1801
|
-
}
|
|
1802
|
-
|
|
1803
|
-
// 显示工具结果
|
|
1804
|
-
if (msg.toolResults && msg.toolResults.length > 0) {
|
|
1805
|
-
msg.toolResults.forEach(function(result) {
|
|
1806
|
-
if (result.text) {
|
|
1807
|
-
var resultText = result.text.length > 200 ? result.text.substring(0, 200) + '...' : result.text;
|
|
1808
|
-
html += '<div class="message-tool-call">';
|
|
1809
|
-
html += '<div class="message-tool-name">📝 工具结果</div>';
|
|
1810
|
-
html += '<div class="message-tool-result">' + escapeHtml(resultText) + '</div>';
|
|
1811
|
-
html += '</div>';
|
|
1812
|
-
}
|
|
1813
|
-
});
|
|
1814
|
-
}
|
|
1471
|
+
// 关闭历史栏
|
|
1472
|
+
toggleHistoryBar();
|
|
1815
1473
|
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
});
|
|
1474
|
+
// 清空终端
|
|
1475
|
+
term.clear();
|
|
1819
1476
|
|
|
1820
|
-
|
|
1477
|
+
// 显示正在恢复的提示
|
|
1478
|
+
term.write('\x1b[1;36m╔══════════════════════════════════════════════════════════════╗\x1b[0m\r\n');
|
|
1479
|
+
term.write('\x1b[1;36m║ \x1b[1;37m正在恢复会话... \x1b[1;36m║\x1b[0m\r\n');
|
|
1480
|
+
term.write('\x1b[1;36m╚══════════════════════════════════════════════════════════════╝\x1b[0m\r\n');
|
|
1481
|
+
term.write('\r\n');
|
|
1821
1482
|
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1483
|
+
if (ws && ws.readyState === 1) {
|
|
1484
|
+
if (currentMode !== 'opencode') {
|
|
1485
|
+
term.write('\x1b[31m错误: 请先切换到 OpenCode 模式\x1b[0m\r\n');
|
|
1486
|
+
return;
|
|
1487
|
+
}
|
|
1488
|
+
term.write('\x1b[33m正在重启 OpenCode 并恢复会话: ' + session.id + '\x1b[0m\r\n');
|
|
1489
|
+
term.write('\r\n');
|
|
1490
|
+
ws.send(JSON.stringify({ type: 'restore', sessionId: session.id }));
|
|
1491
|
+
} else {
|
|
1492
|
+
term.write('\x1b[31m错误: WebSocket 未连接\x1b[0m\r\n');
|
|
1493
|
+
}
|
|
1829
1494
|
}
|
|
1830
1495
|
|
|
1831
1496
|
function escapeHtml(text) {
|
|
@@ -1878,53 +1543,6 @@
|
|
|
1878
1543
|
toggleHistoryBar();
|
|
1879
1544
|
});
|
|
1880
1545
|
|
|
1881
|
-
// 返回到会话列表
|
|
1882
|
-
document.getElementById('back-to-list').addEventListener('click', function(e) {
|
|
1883
|
-
e.stopPropagation();
|
|
1884
|
-
showSessionList();
|
|
1885
|
-
});
|
|
1886
|
-
|
|
1887
|
-
// 恢复会话
|
|
1888
|
-
document.getElementById('restore-session').addEventListener('click', function(e) {
|
|
1889
|
-
e.stopPropagation();
|
|
1890
|
-
if (!currentSessionData) {
|
|
1891
|
-
console.error('[restore] 没有当前会话数据');
|
|
1892
|
-
return;
|
|
1893
|
-
}
|
|
1894
|
-
|
|
1895
|
-
console.log('[restore] 恢复会话:', currentSessionData.id);
|
|
1896
|
-
|
|
1897
|
-
// 关闭历史栏
|
|
1898
|
-
toggleHistoryBar();
|
|
1899
|
-
|
|
1900
|
-
// 清空终端
|
|
1901
|
-
term.clear();
|
|
1902
|
-
|
|
1903
|
-
// 显示正在恢复的提示
|
|
1904
|
-
term.write('\x1b[1;36m╔══════════════════════════════════════════════════════════════╗\x1b[0m\r\n');
|
|
1905
|
-
term.write('\x1b[1;36m║ \x1b[1;37m正在恢复会话... \x1b[1;36m║\x1b[0m\r\n');
|
|
1906
|
-
term.write('\x1b[1;36m╚══════════════════════════════════════════════════════════════╝\x1b[0m\r\n');
|
|
1907
|
-
term.write('\r\n');
|
|
1908
|
-
|
|
1909
|
-
// 发送恢复会话的请求到服务端
|
|
1910
|
-
if (ws && ws.readyState === 1) {
|
|
1911
|
-
if (currentMode !== 'opencode') {
|
|
1912
|
-
term.write('\x1b[31m错误: 请先切换到 OpenCode 模式\x1b[0m\r\n');
|
|
1913
|
-
return;
|
|
1914
|
-
}
|
|
1915
|
-
|
|
1916
|
-
term.write('\x1b[33m正在重启 OpenCode 并恢复会话: ' + currentSessionData.id + '\x1b[0m\r\n');
|
|
1917
|
-
term.write('\r\n');
|
|
1918
|
-
|
|
1919
|
-
// 发送恢复请求
|
|
1920
|
-
ws.send(JSON.stringify({
|
|
1921
|
-
type: 'restore',
|
|
1922
|
-
sessionId: currentSessionData.id
|
|
1923
|
-
}));
|
|
1924
|
-
} else {
|
|
1925
|
-
term.write('\x1b[31m错误: WebSocket 未连接\x1b[0m\r\n');
|
|
1926
|
-
}
|
|
1927
|
-
});
|
|
1928
1546
|
|
|
1929
1547
|
// 提取终端缓冲区文本
|
|
1930
1548
|
function getTerminalText() {
|
|
@@ -1963,15 +1581,6 @@
|
|
|
1963
1581
|
showCopyToast();
|
|
1964
1582
|
}
|
|
1965
1583
|
|
|
1966
|
-
function showCopyPopup(text) {
|
|
1967
|
-
var popup = document.getElementById('copy-popup');
|
|
1968
|
-
var ta = document.getElementById('copy-popup-text');
|
|
1969
|
-
ta.value = text;
|
|
1970
|
-
popup.classList.add('show');
|
|
1971
|
-
ta.focus();
|
|
1972
|
-
ta.select();
|
|
1973
|
-
}
|
|
1974
|
-
|
|
1975
1584
|
function showCopyToast() {
|
|
1976
1585
|
var toast = document.getElementById('copy-toast');
|
|
1977
1586
|
toast.classList.add('show');
|
|
@@ -2050,7 +1659,7 @@
|
|
|
2050
1659
|
fileList.innerHTML = '<div class="git-diff-loading">加载中...</div>';
|
|
2051
1660
|
document.getElementById('git-diff-count').textContent = '0';
|
|
2052
1661
|
|
|
2053
|
-
fetch('/api/git-status')
|
|
1662
|
+
fetch(basePath + '/api/git-status')
|
|
2054
1663
|
.then(function(r) { return r.json(); })
|
|
2055
1664
|
.then(function(data) {
|
|
2056
1665
|
diffChanges = data.changes || [];
|
|
@@ -2098,7 +1707,7 @@
|
|
|
2098
1707
|
var area = document.getElementById('git-diff-content-area');
|
|
2099
1708
|
area.innerHTML = '<div class="git-diff-loading">加载 diff...</div>';
|
|
2100
1709
|
|
|
2101
|
-
fetch('/api/git-diff?files=' + encodeURIComponent(file))
|
|
1710
|
+
fetch(basePath + '/api/git-diff?files=' + encodeURIComponent(file))
|
|
2102
1711
|
.then(function(r) { return r.json(); })
|
|
2103
1712
|
.then(function(data) {
|
|
2104
1713
|
if (!data.diffs || !data.diffs[0]) {
|
package/index.html
CHANGED
|
@@ -87,170 +87,6 @@
|
|
|
87
87
|
-webkit-overflow-scrolling: touch;
|
|
88
88
|
}
|
|
89
89
|
|
|
90
|
-
#session-detail-view {
|
|
91
|
-
display: none;
|
|
92
|
-
flex-direction: column;
|
|
93
|
-
flex: 1;
|
|
94
|
-
min-height: 0;
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
#session-detail-view.visible {
|
|
98
|
-
display: flex;
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
#session-detail-header {
|
|
102
|
-
padding: 12px 16px;
|
|
103
|
-
background: #0d0d0d;
|
|
104
|
-
border-bottom: 1px solid #222;
|
|
105
|
-
flex-shrink: 0;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
#session-detail-title {
|
|
109
|
-
font-size: 14px;
|
|
110
|
-
color: #ddd;
|
|
111
|
-
font-weight: 500;
|
|
112
|
-
margin-bottom: 4px;
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
#session-detail-meta {
|
|
116
|
-
font-size: 11px;
|
|
117
|
-
color: #888;
|
|
118
|
-
display: flex;
|
|
119
|
-
gap: 12px;
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
#session-detail-content {
|
|
123
|
-
flex: 1;
|
|
124
|
-
overflow-y: auto;
|
|
125
|
-
padding: 16px;
|
|
126
|
-
-webkit-overflow-scrolling: touch;
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
.message-item {
|
|
130
|
-
margin-bottom: 20px;
|
|
131
|
-
display: flex;
|
|
132
|
-
gap: 12px;
|
|
133
|
-
align-items: flex-start;
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
/* User 消息:左对齐 */
|
|
137
|
-
.message-user {
|
|
138
|
-
justify-content: flex-start;
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
/* Assistant 消息:右对齐 */
|
|
142
|
-
.message-assistant {
|
|
143
|
-
justify-content: flex-end;
|
|
144
|
-
flex-direction: row-reverse;
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
.message-avatar {
|
|
148
|
-
flex-shrink: 0;
|
|
149
|
-
width: 32px;
|
|
150
|
-
height: 32px;
|
|
151
|
-
border-radius: 50%;
|
|
152
|
-
background: #2a4a7c;
|
|
153
|
-
display: flex;
|
|
154
|
-
align-items: center;
|
|
155
|
-
justify-content: center;
|
|
156
|
-
font-size: 14px;
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
.message-assistant .message-avatar {
|
|
160
|
-
background: #1a5a3a;
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
.message-content {
|
|
164
|
-
flex: 1;
|
|
165
|
-
min-width: 0;
|
|
166
|
-
max-width: 80%;
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
.message-header {
|
|
170
|
-
display: flex;
|
|
171
|
-
align-items: center;
|
|
172
|
-
gap: 8px;
|
|
173
|
-
margin-bottom: 6px;
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
/* User 的标题左对齐 */
|
|
177
|
-
.message-user .message-header {
|
|
178
|
-
justify-content: flex-start;
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
/* Assistant 的标题右对齐 */
|
|
182
|
-
.message-assistant .message-header {
|
|
183
|
-
justify-content: flex-end;
|
|
184
|
-
flex-direction: row-reverse;
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
.message-role {
|
|
188
|
-
font-size: 11px;
|
|
189
|
-
color: #888;
|
|
190
|
-
font-weight: 600;
|
|
191
|
-
text-transform: uppercase;
|
|
192
|
-
letter-spacing: 0.5px;
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
.message-text {
|
|
196
|
-
color: #ddd;
|
|
197
|
-
font-size: 13px;
|
|
198
|
-
line-height: 1.6;
|
|
199
|
-
white-space: pre-wrap;
|
|
200
|
-
word-break: break-word;
|
|
201
|
-
background: #141414;
|
|
202
|
-
padding: 12px;
|
|
203
|
-
border-radius: 8px;
|
|
204
|
-
border: 1px solid #222;
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
/* User 消息气泡 */
|
|
208
|
-
.message-user .message-text {
|
|
209
|
-
background: #1a2332;
|
|
210
|
-
border-color: #2a4a7c;
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
/* Assistant 消息气泡 */
|
|
214
|
-
.message-assistant .message-text {
|
|
215
|
-
background: #1a2e1a;
|
|
216
|
-
border-color: #2a5a3a;
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
.message-tool-call {
|
|
220
|
-
margin-top: 8px;
|
|
221
|
-
padding: 8px 12px;
|
|
222
|
-
background: #1a1a0a;
|
|
223
|
-
border: 1px solid #333;
|
|
224
|
-
border-radius: 6px;
|
|
225
|
-
font-size: 12px;
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
.message-tool-name {
|
|
229
|
-
color: #f0ad4e;
|
|
230
|
-
font-weight: 600;
|
|
231
|
-
display: flex;
|
|
232
|
-
align-items: center;
|
|
233
|
-
gap: 6px;
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
.message-tool-result {
|
|
237
|
-
margin-top: 6px;
|
|
238
|
-
padding: 8px;
|
|
239
|
-
background: #0a0a0a;
|
|
240
|
-
border-radius: 4px;
|
|
241
|
-
font-size: 11px;
|
|
242
|
-
color: #999;
|
|
243
|
-
max-height: 100px;
|
|
244
|
-
overflow-y: auto;
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
.message-empty {
|
|
248
|
-
text-align: center;
|
|
249
|
-
padding: 40px 20px;
|
|
250
|
-
color: #666;
|
|
251
|
-
font-size: 13px;
|
|
252
|
-
}
|
|
253
|
-
|
|
254
90
|
#session-list {
|
|
255
91
|
display: flex;
|
|
256
92
|
flex-direction: column;
|
|
@@ -884,38 +720,6 @@
|
|
|
884
720
|
</div>
|
|
885
721
|
</div>
|
|
886
722
|
|
|
887
|
-
<!-- 会话详情视图 -->
|
|
888
|
-
<div id="session-detail-view">
|
|
889
|
-
<div id="session-history-header">
|
|
890
|
-
<div id="session-history-title">会话详情</div>
|
|
891
|
-
<div id="session-history-actions">
|
|
892
|
-
<button class="history-toggle-btn" id="restore-session" style="background: #1a5a3a; border-color: #2a7a4a;">
|
|
893
|
-
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
894
|
-
<polyline points="1 4 1 10 7 10"></polyline>
|
|
895
|
-
<path d="M3.51 15a9 9 0 1 0 2.13-9.36L1 10"></path>
|
|
896
|
-
</svg>
|
|
897
|
-
<span>恢复会话</span>
|
|
898
|
-
</button>
|
|
899
|
-
<button class="history-toggle-btn" id="back-to-list">
|
|
900
|
-
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
901
|
-
<line x1="19" y1="12" x2="5" y2="12"></line>
|
|
902
|
-
<polyline points="12 19 5 12 12 5"></polyline>
|
|
903
|
-
</svg>
|
|
904
|
-
<span>返回列表</span>
|
|
905
|
-
</button>
|
|
906
|
-
</div>
|
|
907
|
-
</div>
|
|
908
|
-
<div id="session-detail-header">
|
|
909
|
-
<div id="session-detail-title">会话标题</div>
|
|
910
|
-
<div id="session-detail-meta">
|
|
911
|
-
<span id="session-detail-time">时间</span>
|
|
912
|
-
<span id="session-detail-dir">目录</span>
|
|
913
|
-
</div>
|
|
914
|
-
</div>
|
|
915
|
-
<div id="session-detail-content">
|
|
916
|
-
<div class="message-empty">加载中...</div>
|
|
917
|
-
</div>
|
|
918
|
-
</div>
|
|
919
723
|
</div>
|
|
920
724
|
|
|
921
725
|
<!-- Git Diff 面板 -->
|
|
@@ -1046,6 +850,10 @@
|
|
|
1046
850
|
return true;
|
|
1047
851
|
});
|
|
1048
852
|
|
|
853
|
+
// 自动检测反向代理子路径,确保 API/WS 请求带正确前缀
|
|
854
|
+
var basePath = location.pathname.replace(/\/[^/]*$/, '');
|
|
855
|
+
if (basePath === '' || basePath === '/') basePath = '';
|
|
856
|
+
|
|
1049
857
|
var modeSelect = document.getElementById('mode-select');
|
|
1050
858
|
var terminalEl = document.getElementById('terminal');
|
|
1051
859
|
var ws = null;
|
|
@@ -1523,7 +1331,7 @@
|
|
|
1523
1331
|
|
|
1524
1332
|
function connect() {
|
|
1525
1333
|
var proto = location.protocol === 'https:' ? 'wss:' : 'ws:';
|
|
1526
|
-
ws = new WebSocket(proto + '//' + location.host + '/ws');
|
|
1334
|
+
ws = new WebSocket(proto + '//' + location.host + basePath + '/ws');
|
|
1527
1335
|
|
|
1528
1336
|
ws.onopen = function() {
|
|
1529
1337
|
resize();
|
|
@@ -1774,7 +1582,7 @@
|
|
|
1774
1582
|
var sessionList = document.getElementById('session-list');
|
|
1775
1583
|
sessionList.innerHTML = '<div class="session-loading">加载历史会话...</div>';
|
|
1776
1584
|
|
|
1777
|
-
fetch('/api/sessions')
|
|
1585
|
+
fetch(basePath + '/api/sessions')
|
|
1778
1586
|
.then(function(response) { return response.json(); })
|
|
1779
1587
|
.then(function(data) {
|
|
1780
1588
|
sessions = data;
|
|
@@ -1857,7 +1665,7 @@
|
|
|
1857
1665
|
itemEl.style.opacity = '0.4';
|
|
1858
1666
|
itemEl.style.pointerEvents = 'none';
|
|
1859
1667
|
|
|
1860
|
-
fetch('/api/session/' + sessionId, { method: 'DELETE' })
|
|
1668
|
+
fetch(basePath + '/api/session/' + sessionId, { method: 'DELETE' })
|
|
1861
1669
|
.then(function(r) { return r.json(); })
|
|
1862
1670
|
.then(function(data) {
|
|
1863
1671
|
if (data.ok) {
|
|
@@ -1884,110 +1692,35 @@
|
|
|
1884
1692
|
});
|
|
1885
1693
|
}
|
|
1886
1694
|
|
|
1887
|
-
function showSessionDetail() {
|
|
1888
|
-
document.getElementById('session-list-view').classList.add('hidden');
|
|
1889
|
-
document.getElementById('session-detail-view').classList.add('visible');
|
|
1890
|
-
}
|
|
1891
1695
|
|
|
1892
|
-
function showSessionList() {
|
|
1893
|
-
document.getElementById('session-list-view').classList.remove('hidden');
|
|
1894
|
-
document.getElementById('session-detail-view').classList.remove('visible');
|
|
1895
|
-
}
|
|
1896
1696
|
|
|
1897
1697
|
function loadSession(session) {
|
|
1898
|
-
console.log('[
|
|
1899
|
-
|
|
1900
|
-
// 保存当前会话数据
|
|
1698
|
+
console.log('[restore] 直接恢复会话:', session.title);
|
|
1901
1699
|
currentSessionData = session;
|
|
1902
1700
|
|
|
1903
|
-
//
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
// 更新详情页标题和元信息
|
|
1907
|
-
document.getElementById('session-detail-title').textContent = session.preview || session.title;
|
|
1908
|
-
document.getElementById('session-detail-time').textContent = formatTime(session.time_updated);
|
|
1909
|
-
document.getElementById('session-detail-dir').textContent = session.directory.replace(/^\/Users\/[^\/]+/, '~');
|
|
1910
|
-
|
|
1911
|
-
var contentDiv = document.getElementById('session-detail-content');
|
|
1912
|
-
contentDiv.innerHTML = '<div class="message-empty">加载消息中...</div>';
|
|
1913
|
-
|
|
1914
|
-
// 获取会话的所有消息
|
|
1915
|
-
fetch('/api/session/' + session.id)
|
|
1916
|
-
.then(function(response) { return response.json(); })
|
|
1917
|
-
.then(function(messages) {
|
|
1918
|
-
console.log('[session] 收到', messages.length, '条消息');
|
|
1919
|
-
|
|
1920
|
-
if (messages.length === 0) {
|
|
1921
|
-
contentDiv.innerHTML = '<div class="message-empty">该会话暂无消息</div>';
|
|
1922
|
-
return;
|
|
1923
|
-
}
|
|
1924
|
-
|
|
1925
|
-
// 渲染消息列表
|
|
1926
|
-
var html = '';
|
|
1927
|
-
messages.forEach(function(msg) {
|
|
1928
|
-
console.log('[render] 消息:', msg);
|
|
1929
|
-
|
|
1930
|
-
var roleClass = msg.role === 'assistant' ? 'message-assistant' : 'message-user';
|
|
1931
|
-
var avatar = msg.role === 'user' ? '👤' : '🤖';
|
|
1932
|
-
var roleName = msg.role === 'user' ? 'User' : 'Assistant';
|
|
1933
|
-
|
|
1934
|
-
html += '<div class="message-item ' + roleClass + '">';
|
|
1935
|
-
html += '<div class="message-avatar">' + avatar + '</div>';
|
|
1936
|
-
html += '<div class="message-content">';
|
|
1937
|
-
html += '<div class="message-header">';
|
|
1938
|
-
html += '<div class="message-role">' + roleName + '</div>';
|
|
1939
|
-
html += '</div>';
|
|
1940
|
-
|
|
1941
|
-
// 显示推理过程(仅 assistant)
|
|
1942
|
-
if (msg.reasoning) {
|
|
1943
|
-
html += '<div class="message-text" style="background: #1a1a0a; border-color: #333;">';
|
|
1944
|
-
html += '<div style="color: #f0ad4e; font-size: 11px; font-weight: 600; margin-bottom: 6px;">💭 思考过程</div>';
|
|
1945
|
-
html += '<div style="color: #bbb;">' + escapeHtml(msg.reasoning) + '</div>';
|
|
1946
|
-
html += '</div>';
|
|
1947
|
-
}
|
|
1948
|
-
|
|
1949
|
-
// 显示文本内容
|
|
1950
|
-
if (msg.text) {
|
|
1951
|
-
html += '<div class="message-text">' + escapeHtml(msg.text) + '</div>';
|
|
1952
|
-
} else if (!msg.reasoning && !msg.toolCalls && !msg.toolResults) {
|
|
1953
|
-
html += '<div class="message-text" style="color: #666; font-style: italic;">(无文本内容)</div>';
|
|
1954
|
-
}
|
|
1955
|
-
|
|
1956
|
-
// 显示工具调用
|
|
1957
|
-
if (msg.toolCalls && msg.toolCalls.length > 0) {
|
|
1958
|
-
msg.toolCalls.forEach(function(tool) {
|
|
1959
|
-
html += '<div class="message-tool-call">';
|
|
1960
|
-
html += '<div class="message-tool-name">🔧 工具调用: ' + escapeHtml(tool.name || '未知工具') + '</div>';
|
|
1961
|
-
html += '</div>';
|
|
1962
|
-
});
|
|
1963
|
-
}
|
|
1964
|
-
|
|
1965
|
-
// 显示工具结果
|
|
1966
|
-
if (msg.toolResults && msg.toolResults.length > 0) {
|
|
1967
|
-
msg.toolResults.forEach(function(result) {
|
|
1968
|
-
if (result.text) {
|
|
1969
|
-
var resultText = result.text.length > 200 ? result.text.substring(0, 200) + '...' : result.text;
|
|
1970
|
-
html += '<div class="message-tool-call">';
|
|
1971
|
-
html += '<div class="message-tool-name">📝 工具结果</div>';
|
|
1972
|
-
html += '<div class="message-tool-result">' + escapeHtml(resultText) + '</div>';
|
|
1973
|
-
html += '</div>';
|
|
1974
|
-
}
|
|
1975
|
-
});
|
|
1976
|
-
}
|
|
1701
|
+
// 关闭历史栏
|
|
1702
|
+
toggleHistoryBar();
|
|
1977
1703
|
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
});
|
|
1704
|
+
// 清空终端
|
|
1705
|
+
term.clear();
|
|
1981
1706
|
|
|
1982
|
-
|
|
1707
|
+
// 显示正在恢复的提示
|
|
1708
|
+
term.write('\x1b[1;36m╔══════════════════════════════════════════════════════════════╗\x1b[0m\r\n');
|
|
1709
|
+
term.write('\x1b[1;36m║ \x1b[1;37m正在恢复会话... \x1b[1;36m║\x1b[0m\r\n');
|
|
1710
|
+
term.write('\x1b[1;36m╚══════════════════════════════════════════════════════════════╝\x1b[0m\r\n');
|
|
1711
|
+
term.write('\r\n');
|
|
1983
1712
|
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
1713
|
+
if (ws && ws.readyState === 1) {
|
|
1714
|
+
if (currentMode !== 'opencode') {
|
|
1715
|
+
term.write('\x1b[31m错误: 请先切换到 OpenCode 模式\x1b[0m\r\n');
|
|
1716
|
+
return;
|
|
1717
|
+
}
|
|
1718
|
+
term.write('\x1b[33m正在重启 OpenCode 并恢复会话: ' + session.id + '\x1b[0m\r\n');
|
|
1719
|
+
term.write('\r\n');
|
|
1720
|
+
ws.send(JSON.stringify({ type: 'restore', sessionId: session.id }));
|
|
1721
|
+
} else {
|
|
1722
|
+
term.write('\x1b[31m错误: WebSocket 未连接\x1b[0m\r\n');
|
|
1723
|
+
}
|
|
1991
1724
|
}
|
|
1992
1725
|
|
|
1993
1726
|
function escapeHtml(text) {
|
|
@@ -2040,53 +1773,6 @@
|
|
|
2040
1773
|
toggleHistoryBar();
|
|
2041
1774
|
});
|
|
2042
1775
|
|
|
2043
|
-
// 返回到会话列表
|
|
2044
|
-
document.getElementById('back-to-list').addEventListener('click', function(e) {
|
|
2045
|
-
e.stopPropagation();
|
|
2046
|
-
showSessionList();
|
|
2047
|
-
});
|
|
2048
|
-
|
|
2049
|
-
// 恢复会话
|
|
2050
|
-
document.getElementById('restore-session').addEventListener('click', function(e) {
|
|
2051
|
-
e.stopPropagation();
|
|
2052
|
-
if (!currentSessionData) {
|
|
2053
|
-
console.error('[restore] 没有当前会话数据');
|
|
2054
|
-
return;
|
|
2055
|
-
}
|
|
2056
|
-
|
|
2057
|
-
console.log('[restore] 恢复会话:', currentSessionData.id);
|
|
2058
|
-
|
|
2059
|
-
// 关闭历史栏
|
|
2060
|
-
toggleHistoryBar();
|
|
2061
|
-
|
|
2062
|
-
// 清空终端
|
|
2063
|
-
term.clear();
|
|
2064
|
-
|
|
2065
|
-
// 显示正在恢复的提示
|
|
2066
|
-
term.write('\x1b[1;36m╔══════════════════════════════════════════════════════════════╗\x1b[0m\r\n');
|
|
2067
|
-
term.write('\x1b[1;36m║ \x1b[1;37m正在恢复会话... \x1b[1;36m║\x1b[0m\r\n');
|
|
2068
|
-
term.write('\x1b[1;36m╚══════════════════════════════════════════════════════════════╝\x1b[0m\r\n');
|
|
2069
|
-
term.write('\r\n');
|
|
2070
|
-
|
|
2071
|
-
// 发送恢复会话的请求到服务端
|
|
2072
|
-
if (ws && ws.readyState === 1) {
|
|
2073
|
-
if (currentMode !== 'opencode') {
|
|
2074
|
-
term.write('\x1b[31m错误: 请先切换到 OpenCode 模式\x1b[0m\r\n');
|
|
2075
|
-
return;
|
|
2076
|
-
}
|
|
2077
|
-
|
|
2078
|
-
term.write('\x1b[33m正在重启 OpenCode 并恢复会话: ' + currentSessionData.id + '\x1b[0m\r\n');
|
|
2079
|
-
term.write('\r\n');
|
|
2080
|
-
|
|
2081
|
-
// 发送恢复请求
|
|
2082
|
-
ws.send(JSON.stringify({
|
|
2083
|
-
type: 'restore',
|
|
2084
|
-
sessionId: currentSessionData.id
|
|
2085
|
-
}));
|
|
2086
|
-
} else {
|
|
2087
|
-
term.write('\x1b[31m错误: WebSocket 未连接\x1b[0m\r\n');
|
|
2088
|
-
}
|
|
2089
|
-
});
|
|
2090
1776
|
|
|
2091
1777
|
// 提取终端缓冲区文本
|
|
2092
1778
|
function getTerminalText() {
|
|
@@ -2203,7 +1889,7 @@
|
|
|
2203
1889
|
fileList.innerHTML = '<div class="git-diff-loading">加载中...</div>';
|
|
2204
1890
|
document.getElementById('git-diff-count').textContent = '0';
|
|
2205
1891
|
|
|
2206
|
-
fetch('/api/git-status')
|
|
1892
|
+
fetch(basePath + '/api/git-status')
|
|
2207
1893
|
.then(function(r) { return r.json(); })
|
|
2208
1894
|
.then(function(data) {
|
|
2209
1895
|
diffChanges = data.changes || [];
|
|
@@ -2251,7 +1937,7 @@
|
|
|
2251
1937
|
var area = document.getElementById('git-diff-content-area');
|
|
2252
1938
|
area.innerHTML = '<div class="git-diff-loading">加载 diff...</div>';
|
|
2253
1939
|
|
|
2254
|
-
fetch('/api/git-diff?files=' + encodeURIComponent(file))
|
|
1940
|
+
fetch(basePath + '/api/git-diff?files=' + encodeURIComponent(file))
|
|
2255
1941
|
.then(function(r) { return r.json(); })
|
|
2256
1942
|
.then(function(data) {
|
|
2257
1943
|
if (!data.diffs || !data.diffs[0]) {
|
package/package.json
CHANGED
package/server.js
CHANGED
|
@@ -30,7 +30,7 @@ async function getOrCreateCert() {
|
|
|
30
30
|
return { key: readFileSync(keyPath), cert: readFileSync(certPath) };
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
-
console.log('🔐 首次使用 HTTPS,生成自签名证书...');
|
|
33
|
+
if (!JSON_OUTPUT) console.log('🔐 首次使用 HTTPS,生成自签名证书...');
|
|
34
34
|
mkdirSync(certDir, { recursive: true });
|
|
35
35
|
|
|
36
36
|
const { default: selfsigned } = await import('selfsigned');
|
|
@@ -48,7 +48,7 @@ async function getOrCreateCert() {
|
|
|
48
48
|
|
|
49
49
|
writeFileSync(keyPath, pems.private);
|
|
50
50
|
writeFileSync(certPath, pems.cert);
|
|
51
|
-
console.log(`📁 证书已保存到 ${certDir}`);
|
|
51
|
+
if (!JSON_OUTPUT) console.log(`📁 证书已保存到 ${certDir}`);
|
|
52
52
|
return { key: pems.private, cert: pems.cert };
|
|
53
53
|
}
|
|
54
54
|
|