claude-opencode-viewer 2.6.43 → 2.6.44

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.
Files changed (3) hide show
  1. package/index.html +59 -23
  2. package/package.json +1 -1
  3. package/server.js +3 -2
package/index.html CHANGED
@@ -4,7 +4,33 @@
4
4
  <meta charset="UTF-8" />
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover">
6
6
  <title>Claude OpenCode Viewer</title>
7
- <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@xterm/xterm@5.5.0/css/xterm.min.css">
7
+ <style id="loading-style">
8
+ @keyframes loading-dots {
9
+ 0% { content: ''; }
10
+ 25% { content: '.'; }
11
+ 50% { content: '..'; }
12
+ 75% { content: '...'; }
13
+ }
14
+ #loading-overlay {
15
+ position: fixed;
16
+ top: 0; left: 0; right: 0; bottom: 0;
17
+ background: #0a0a0a;
18
+ color: #ccc;
19
+ display: flex;
20
+ align-items: center;
21
+ justify-content: center;
22
+ font-size: 16px;
23
+ font-family: -apple-system, BlinkMacSystemFont, sans-serif;
24
+ letter-spacing: 1px;
25
+ z-index: 99999;
26
+ }
27
+ #loading-overlay::after {
28
+ content: '';
29
+ animation: loading-dots 1.2s steps(4, end) infinite;
30
+ }
31
+ #loading-overlay.hidden { display: none !important; }
32
+ </style>
33
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@xterm/xterm@5.5.0/css/xterm.min.css" media="print" onload="this.media='all'">
8
34
  <style>
9
35
  * { margin: 0; padding: 0; box-sizing: border-box; }
10
36
  html, body { margin: 0; padding: 0; overflow: hidden; }
@@ -359,19 +385,6 @@
359
385
  background: #0a0a0a;
360
386
  position: relative;
361
387
  }
362
- #loading-overlay {
363
- position: fixed;
364
- top: 0; left: 0; right: 0; bottom: 0;
365
- background: #0a0a0a;
366
- color: #888;
367
- display: flex;
368
- align-items: center;
369
- justify-content: center;
370
- font-size: 15px;
371
- z-index: 9999;
372
- }
373
- #loading-overlay.hidden { display: none; }
374
-
375
388
  #terminal {
376
389
  flex: 1;
377
390
  overflow: hidden;
@@ -857,7 +870,7 @@
857
870
  </head>
858
871
  <body>
859
872
  <!-- 参考 cc-viewer 的 App.jsx 行 1315-1607: 完整的移动端布局结构 -->
860
- <div id="loading-overlay">正在初始化...</div>
873
+ <div id="loading-overlay">正在初始化</div>
861
874
  <div id="layout">
862
875
  <div id="header">
863
876
  <div style="display: flex; gap: 4px; align-items: center; overflow-x: auto; flex: 1; min-width: 0;">
@@ -1587,6 +1600,7 @@
1587
1600
  ws = new WebSocket(proto + '//' + location.host + basePath + '/ws');
1588
1601
 
1589
1602
  ws.onopen = function() {
1603
+ setLoadingText('正在连接服务');
1590
1604
  resize();
1591
1605
  rebindTouchScroll();
1592
1606
  };
@@ -1599,9 +1613,24 @@
1599
1613
  };
1600
1614
 
1601
1615
  var loadingOverlay = document.getElementById('loading-overlay');
1602
- function hideLoading() {
1616
+ var loadingShowTime = Date.now();
1617
+ var loadingMinMs = 600;
1618
+ var loadingHideTimer = null;
1619
+ function setLoadingText(text) {
1603
1620
  if (loadingOverlay && !loadingOverlay.classList.contains('hidden')) {
1621
+ loadingOverlay.textContent = text;
1622
+ }
1623
+ }
1624
+ function hideLoading() {
1625
+ if (!loadingOverlay || loadingOverlay.classList.contains('hidden')) return;
1626
+ if (loadingHideTimer) return; // 已在等待中
1627
+ var elapsed = Date.now() - loadingShowTime;
1628
+ if (elapsed >= loadingMinMs) {
1604
1629
  loadingOverlay.classList.add('hidden');
1630
+ } else {
1631
+ loadingHideTimer = setTimeout(function() {
1632
+ loadingOverlay.classList.add('hidden');
1633
+ }, loadingMinMs - elapsed);
1605
1634
  }
1606
1635
  }
1607
1636
 
@@ -1621,12 +1650,25 @@
1621
1650
  }
1622
1651
  }
1623
1652
  else if (msg.type === 'state') {
1653
+ // 重连时清掉旧终端内容(如"连接断开"提示)
1654
+ if (writeTimer) { cancelAnimationFrame(writeTimer); writeTimer = null; }
1655
+ writeBuffer = '';
1656
+ term.reset();
1657
+ term.clear();
1658
+ // 同步模式 UI
1659
+ if (msg.mode) {
1660
+ currentMode = msg.mode;
1661
+ modeSelect.value = msg.mode;
1662
+ }
1624
1663
  if (msg.running) {
1664
+ setLoadingText('正在连接');
1665
+ hideLoading();
1625
1666
  preloadData();
1626
1667
  }
1627
1668
  // 服务端还没启动进程时,查最近会话并自动恢复
1628
1669
  if (!msg.running && !mobileInitSent) {
1629
1670
  mobileInitSent = true;
1671
+ setLoadingText('正在查询会话');
1630
1672
  fetch('/api/last-sessions')
1631
1673
  .then(function(r) { return r.json(); })
1632
1674
  .then(function(data) {
@@ -1641,6 +1683,7 @@
1641
1683
  claudeProject = cl.project;
1642
1684
  }
1643
1685
  currentMode = mode;
1686
+ setLoadingText('正在启动 ' + (mode === 'claude' ? 'Claude' : 'OpenCode'));
1644
1687
  ws.send(JSON.stringify({ type: 'init', mode: mode, sessionId: sessionId || null }));
1645
1688
  })
1646
1689
  .catch(function() {
@@ -1663,13 +1706,6 @@
1663
1706
  writeBuffer = '';
1664
1707
  term.clear();
1665
1708
  }
1666
- else if (msg.type === 'state') {
1667
- if (msg.mode) {
1668
- currentMode = msg.mode;
1669
- modeSelect.value = msg.mode;
1670
- modeIndicator.textContent = msg.mode === 'claude' ? 'Claude' : 'OpenCode';
1671
- }
1672
- }
1673
1709
  else if (msg.type === 'restored') {
1674
1710
  // 会话恢复成功,清除所有残留
1675
1711
  if (writeTimer) { cancelAnimationFrame(writeTimer); writeTimer = null; }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-opencode-viewer",
3
- "version": "2.6.43",
3
+ "version": "2.6.44",
4
4
  "description": "A unified terminal viewer for Claude Code and OpenCode with seamless switching",
5
5
  "type": "module",
6
6
  "main": "server.js",
package/server.js CHANGED
@@ -295,6 +295,8 @@ async function spawnProcess(mode, sessionId = null) {
295
295
  });
296
296
 
297
297
  proc.onData((data) => {
298
+ // 忽略已被替换的旧进程输出
299
+ if (currentProcess !== proc) return;
298
300
  outputBuffer += data;
299
301
  if (outputBuffer.length > MAX_BUFFER) {
300
302
  const rawStart = outputBuffer.length - MAX_BUFFER;
@@ -1216,8 +1218,7 @@ wssInst.on('connection', (ws, req) => {
1216
1218
  outputBuffer = '';
1217
1219
  try {
1218
1220
  await spawnProcess(mode, msg.sessionId || null);
1219
- ws.send(JSON.stringify({ type: 'mode', mode: currentMode }));
1220
- ws.send(JSON.stringify({ type: 'started', sessionId: msg.sessionId || null }));
1221
+ ws.send(JSON.stringify({ type: 'started', mode: currentMode, sessionId: msg.sessionId || null }));
1221
1222
  } catch (e) {
1222
1223
  LOG('[init] 启动失败:', e.message);
1223
1224
  ws.send(JSON.stringify({ type: 'start-error', error: e.message }));