claude-opencode-viewer 2.6.10 → 2.6.11
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 +21 -339
- package/index.html +21 -339
- 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;
|
|
@@ -894,38 +730,6 @@
|
|
|
894
730
|
</div>
|
|
895
731
|
</div>
|
|
896
732
|
|
|
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
733
|
</div>
|
|
930
734
|
|
|
931
735
|
<!-- Git Diff 面板 -->
|
|
@@ -1722,110 +1526,35 @@
|
|
|
1722
1526
|
});
|
|
1723
1527
|
}
|
|
1724
1528
|
|
|
1725
|
-
function showSessionDetail() {
|
|
1726
|
-
document.getElementById('session-list-view').classList.add('hidden');
|
|
1727
|
-
document.getElementById('session-detail-view').classList.add('visible');
|
|
1728
|
-
}
|
|
1729
1529
|
|
|
1730
|
-
function showSessionList() {
|
|
1731
|
-
document.getElementById('session-list-view').classList.remove('hidden');
|
|
1732
|
-
document.getElementById('session-detail-view').classList.remove('visible');
|
|
1733
|
-
}
|
|
1734
1530
|
|
|
1735
1531
|
function loadSession(session) {
|
|
1736
|
-
console.log('[
|
|
1737
|
-
|
|
1738
|
-
// 保存当前会话数据
|
|
1532
|
+
console.log('[restore] 直接恢复会话:', session.title);
|
|
1739
1533
|
currentSessionData = session;
|
|
1740
1534
|
|
|
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
|
-
}
|
|
1535
|
+
// 关闭历史栏
|
|
1536
|
+
toggleHistoryBar();
|
|
1815
1537
|
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
});
|
|
1538
|
+
// 清空终端
|
|
1539
|
+
term.clear();
|
|
1819
1540
|
|
|
1820
|
-
|
|
1541
|
+
// 显示正在恢复的提示
|
|
1542
|
+
term.write('\x1b[1;36m╔══════════════════════════════════════════════════════════════╗\x1b[0m\r\n');
|
|
1543
|
+
term.write('\x1b[1;36m║ \x1b[1;37m正在恢复会话... \x1b[1;36m║\x1b[0m\r\n');
|
|
1544
|
+
term.write('\x1b[1;36m╚══════════════════════════════════════════════════════════════╝\x1b[0m\r\n');
|
|
1545
|
+
term.write('\r\n');
|
|
1821
1546
|
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1547
|
+
if (ws && ws.readyState === 1) {
|
|
1548
|
+
if (currentMode !== 'opencode') {
|
|
1549
|
+
term.write('\x1b[31m错误: 请先切换到 OpenCode 模式\x1b[0m\r\n');
|
|
1550
|
+
return;
|
|
1551
|
+
}
|
|
1552
|
+
term.write('\x1b[33m正在重启 OpenCode 并恢复会话: ' + session.id + '\x1b[0m\r\n');
|
|
1553
|
+
term.write('\r\n');
|
|
1554
|
+
ws.send(JSON.stringify({ type: 'restore', sessionId: session.id }));
|
|
1555
|
+
} else {
|
|
1556
|
+
term.write('\x1b[31m错误: WebSocket 未连接\x1b[0m\r\n');
|
|
1557
|
+
}
|
|
1829
1558
|
}
|
|
1830
1559
|
|
|
1831
1560
|
function escapeHtml(text) {
|
|
@@ -1878,53 +1607,6 @@
|
|
|
1878
1607
|
toggleHistoryBar();
|
|
1879
1608
|
});
|
|
1880
1609
|
|
|
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
1610
|
|
|
1929
1611
|
// 提取终端缓冲区文本
|
|
1930
1612
|
function getTerminalText() {
|
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 面板 -->
|
|
@@ -1884,110 +1688,35 @@
|
|
|
1884
1688
|
});
|
|
1885
1689
|
}
|
|
1886
1690
|
|
|
1887
|
-
function showSessionDetail() {
|
|
1888
|
-
document.getElementById('session-list-view').classList.add('hidden');
|
|
1889
|
-
document.getElementById('session-detail-view').classList.add('visible');
|
|
1890
|
-
}
|
|
1891
1691
|
|
|
1892
|
-
function showSessionList() {
|
|
1893
|
-
document.getElementById('session-list-view').classList.remove('hidden');
|
|
1894
|
-
document.getElementById('session-detail-view').classList.remove('visible');
|
|
1895
|
-
}
|
|
1896
1692
|
|
|
1897
1693
|
function loadSession(session) {
|
|
1898
|
-
console.log('[
|
|
1899
|
-
|
|
1900
|
-
// 保存当前会话数据
|
|
1694
|
+
console.log('[restore] 直接恢复会话:', session.title);
|
|
1901
1695
|
currentSessionData = session;
|
|
1902
1696
|
|
|
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
|
-
}
|
|
1697
|
+
// 关闭历史栏
|
|
1698
|
+
toggleHistoryBar();
|
|
1977
1699
|
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
});
|
|
1700
|
+
// 清空终端
|
|
1701
|
+
term.clear();
|
|
1981
1702
|
|
|
1982
|
-
|
|
1703
|
+
// 显示正在恢复的提示
|
|
1704
|
+
term.write('\x1b[1;36m╔══════════════════════════════════════════════════════════════╗\x1b[0m\r\n');
|
|
1705
|
+
term.write('\x1b[1;36m║ \x1b[1;37m正在恢复会话... \x1b[1;36m║\x1b[0m\r\n');
|
|
1706
|
+
term.write('\x1b[1;36m╚══════════════════════════════════════════════════════════════╝\x1b[0m\r\n');
|
|
1707
|
+
term.write('\r\n');
|
|
1983
1708
|
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
1709
|
+
if (ws && ws.readyState === 1) {
|
|
1710
|
+
if (currentMode !== 'opencode') {
|
|
1711
|
+
term.write('\x1b[31m错误: 请先切换到 OpenCode 模式\x1b[0m\r\n');
|
|
1712
|
+
return;
|
|
1713
|
+
}
|
|
1714
|
+
term.write('\x1b[33m正在重启 OpenCode 并恢复会话: ' + session.id + '\x1b[0m\r\n');
|
|
1715
|
+
term.write('\r\n');
|
|
1716
|
+
ws.send(JSON.stringify({ type: 'restore', sessionId: session.id }));
|
|
1717
|
+
} else {
|
|
1718
|
+
term.write('\x1b[31m错误: WebSocket 未连接\x1b[0m\r\n');
|
|
1719
|
+
}
|
|
1991
1720
|
}
|
|
1992
1721
|
|
|
1993
1722
|
function escapeHtml(text) {
|
|
@@ -2040,53 +1769,6 @@
|
|
|
2040
1769
|
toggleHistoryBar();
|
|
2041
1770
|
});
|
|
2042
1771
|
|
|
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
1772
|
|
|
2091
1773
|
// 提取终端缓冲区文本
|
|
2092
1774
|
function getTerminalText() {
|
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
|
|