claude-opencode-viewer 2.6.42 → 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.
package/index-pc.html CHANGED
@@ -2262,29 +2262,28 @@
2262
2262
  var bar = document.getElementById('git-diff-bar');
2263
2263
  if (diffBarVisible) {
2264
2264
  bar.classList.add('visible');
2265
- loadGitStatus(false);
2265
+ if (cachedGitStatus) {
2266
+ diffChanges = cachedGitStatus.changes || [];
2267
+ document.getElementById('git-diff-count').textContent = diffChanges.length;
2268
+ renderDiffFileList();
2269
+ } else if (gitStatusLoading) {
2270
+ document.getElementById('git-diff-file-list').innerHTML = '<div class="git-diff-loading">正在查询 git status...</div>';
2271
+ document.getElementById('git-diff-count').textContent = '...';
2272
+ } else {
2273
+ document.getElementById('git-diff-file-list').innerHTML = '<div class="git-diff-loading" style="color:#666;">点击刷新加载</div>';
2274
+ }
2266
2275
  } else {
2267
2276
  bar.classList.remove('visible');
2268
2277
  diffSelectedFile = null;
2269
2278
  }
2270
2279
  }
2271
2280
 
2272
- function loadGitStatus(forceRefresh) {
2281
+ function loadGitStatus() {
2273
2282
  var fileList = document.getElementById('git-diff-file-list');
2274
-
2275
- if (cachedGitStatus && !forceRefresh) {
2276
- diffChanges = cachedGitStatus.changes || [];
2277
- document.getElementById('git-diff-count').textContent = diffChanges.length;
2278
- renderDiffFileList();
2279
- return;
2280
- }
2281
-
2282
- fileList.innerHTML = '<div class="git-diff-loading">' + (forceRefresh ? '正在刷新...' : '正在查询 git status...') + '</div>';
2283
+ fileList.innerHTML = '<div class="git-diff-loading">正在刷新...</div>';
2283
2284
  document.getElementById('git-diff-count').textContent = '...';
2284
-
2285
- if (gitStatusLoading && !forceRefresh) return;
2286
2285
  gitStatusLoading = true;
2287
- if (forceRefresh) cachedGitStatus = null;
2286
+ cachedGitStatus = null;
2288
2287
 
2289
2288
  fetch(basePath + '/api/git-status')
2290
2289
  .then(function(r) { return r.json(); })
@@ -2426,7 +2425,7 @@
2426
2425
  document.getElementById('close-diff').addEventListener('click', toggleDiffBar);
2427
2426
  document.getElementById('refresh-diff').addEventListener('click', function(e) {
2428
2427
  e.stopPropagation();
2429
- loadGitStatus(true);
2428
+ loadGitStatus();
2430
2429
  // 重置 diff 内容区
2431
2430
  diffSelectedFile = null;
2432
2431
  document.getElementById('git-diff-content-area').innerHTML =
@@ -2448,7 +2447,14 @@
2448
2447
  var bar = document.getElementById('docs-bar');
2449
2448
  if (docsBarVisible) {
2450
2449
  bar.classList.add('visible');
2451
- loadDocs(false);
2450
+ if (cachedDocs) {
2451
+ renderDocsList(cachedDocs);
2452
+ } else if (docsLoading) {
2453
+ document.getElementById('docs-file-list').innerHTML = '<div class="docs-loading">正在查询文档...</div>';
2454
+ document.getElementById('docs-count').textContent = '...';
2455
+ } else {
2456
+ document.getElementById('docs-file-list').innerHTML = '<div class="docs-loading" style="color:#666;">点击刷新加载</div>';
2457
+ }
2452
2458
  } else {
2453
2459
  bar.classList.remove('visible');
2454
2460
  docsSelectedFile = null;
@@ -2484,20 +2490,12 @@
2484
2490
  });
2485
2491
  }
2486
2492
 
2487
- function loadDocs(forceRefresh) {
2493
+ function loadDocs() {
2488
2494
  var fileList = document.getElementById('docs-file-list');
2489
-
2490
- if (cachedDocs && !forceRefresh) {
2491
- renderDocsList(cachedDocs);
2492
- return;
2493
- }
2494
-
2495
- fileList.innerHTML = '<div class="docs-loading">' + (forceRefresh ? '正在刷新...' : '正在查询文档...') + '</div>';
2495
+ fileList.innerHTML = '<div class="docs-loading">正在刷新...</div>';
2496
2496
  document.getElementById('docs-count').textContent = '...';
2497
-
2498
- if (docsLoading && !forceRefresh) return;
2499
2497
  docsLoading = true;
2500
- if (forceRefresh) cachedDocs = null;
2498
+ cachedDocs = null;
2501
2499
 
2502
2500
  fetch(basePath + '/api/docs')
2503
2501
  .then(function(r) { return r.json(); })
@@ -2533,7 +2531,7 @@
2533
2531
  document.getElementById('close-docs').addEventListener('click', toggleDocsBar);
2534
2532
  document.getElementById('refresh-docs').addEventListener('click', function(e) {
2535
2533
  e.stopPropagation();
2536
- loadDocs(true);
2534
+ loadDocs();
2537
2535
  docsSelectedFile = null;
2538
2536
  document.getElementById('docs-content-area').innerHTML =
2539
2537
  '<div class="docs-placeholder">' +
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; }
@@ -2517,31 +2553,30 @@
2517
2553
  var bar = document.getElementById('git-diff-bar');
2518
2554
  if (diffBarVisible) {
2519
2555
  bar.classList.add('visible');
2520
- loadGitStatus(false);
2556
+ if (cachedGitStatus) {
2557
+ diffChanges = cachedGitStatus.changes || [];
2558
+ document.getElementById('git-diff-count').textContent = diffChanges.length;
2559
+ renderDiffFileList();
2560
+ } else if (gitStatusLoading) {
2561
+ document.getElementById('git-diff-file-list').innerHTML = '<div class="git-diff-loading">正在查询 git status...</div>';
2562
+ document.getElementById('git-diff-count').textContent = '...';
2563
+ } else {
2564
+ document.getElementById('git-diff-file-list').innerHTML = '<div class="git-diff-loading" style="color:#666;">点击刷新加载</div>';
2565
+ }
2521
2566
  } else {
2522
2567
  bar.classList.remove('visible');
2523
2568
  diffSelectedFile = null;
2524
2569
  }
2525
2570
  }
2526
2571
 
2527
- function loadGitStatus(forceRefresh) {
2572
+ function loadGitStatus() {
2528
2573
  var fileList = document.getElementById('git-diff-file-list');
2529
2574
 
2530
- // 有缓存且非强制刷新,直接用缓存
2531
- if (cachedGitStatus && !forceRefresh) {
2532
- diffChanges = cachedGitStatus.changes || [];
2533
- document.getElementById('git-diff-count').textContent = diffChanges.length;
2534
- renderDiffFileList();
2535
- return;
2536
- }
2537
-
2538
- // 正在加载中或需要发起请求
2539
- fileList.innerHTML = '<div class="git-diff-loading">' + (forceRefresh ? '正在刷新...' : '正在查询 git status...') + '</div>';
2575
+ fileList.innerHTML = '<div class="git-diff-loading">正在刷新...</div>';
2540
2576
  document.getElementById('git-diff-count').textContent = '...';
2541
2577
 
2542
- if (gitStatusLoading && !forceRefresh) return; // 预加载进行中,等它完成
2543
2578
  gitStatusLoading = true;
2544
- if (forceRefresh) cachedGitStatus = null;
2579
+ cachedGitStatus = null;
2545
2580
 
2546
2581
  fetch(basePath + '/api/git-status')
2547
2582
  .then(function(r) { return r.json(); })
@@ -2683,7 +2718,7 @@
2683
2718
  document.getElementById('close-diff').addEventListener('click', toggleDiffBar);
2684
2719
  document.getElementById('refresh-diff').addEventListener('click', function(e) {
2685
2720
  e.stopPropagation();
2686
- loadGitStatus(true);
2721
+ loadGitStatus();
2687
2722
  // 重置 diff 内容区
2688
2723
  diffSelectedFile = null;
2689
2724
  document.getElementById('git-diff-content-area').innerHTML =
@@ -2705,7 +2740,14 @@
2705
2740
  var bar = document.getElementById('docs-bar');
2706
2741
  if (docsBarVisible) {
2707
2742
  bar.classList.add('visible');
2708
- loadDocs(false);
2743
+ if (cachedDocs) {
2744
+ renderDocsList(cachedDocs);
2745
+ } else if (docsLoading) {
2746
+ document.getElementById('docs-file-list').innerHTML = '<div class="docs-loading">正在查询文档...</div>';
2747
+ document.getElementById('docs-count').textContent = '...';
2748
+ } else {
2749
+ document.getElementById('docs-file-list').innerHTML = '<div class="docs-loading" style="color:#666;">点击刷新加载</div>';
2750
+ }
2709
2751
  } else {
2710
2752
  bar.classList.remove('visible');
2711
2753
  docsSelectedFile = null;
@@ -2741,21 +2783,14 @@
2741
2783
  });
2742
2784
  }
2743
2785
 
2744
- function loadDocs(forceRefresh) {
2786
+ function loadDocs() {
2745
2787
  var fileList = document.getElementById('docs-file-list');
2746
2788
 
2747
- // 有缓存且非强制刷新,直接用缓存
2748
- if (cachedDocs && !forceRefresh) {
2749
- renderDocsList(cachedDocs);
2750
- return;
2751
- }
2752
-
2753
- fileList.innerHTML = '<div class="docs-loading">' + (forceRefresh ? '正在刷新...' : '正在查询文档...') + '</div>';
2789
+ fileList.innerHTML = '<div class="docs-loading">正在刷新...</div>';
2754
2790
  document.getElementById('docs-count').textContent = '...';
2755
2791
 
2756
- if (docsLoading && !forceRefresh) return;
2757
2792
  docsLoading = true;
2758
- if (forceRefresh) cachedDocs = null;
2793
+ cachedDocs = null;
2759
2794
 
2760
2795
  fetch(basePath + '/api/docs')
2761
2796
  .then(function(r) { return r.json(); })
@@ -2791,7 +2826,7 @@
2791
2826
  document.getElementById('close-docs').addEventListener('click', toggleDocsBar);
2792
2827
  document.getElementById('refresh-docs').addEventListener('click', function(e) {
2793
2828
  e.stopPropagation();
2794
- loadDocs(true);
2829
+ loadDocs();
2795
2830
  docsSelectedFile = null;
2796
2831
  document.getElementById('docs-content-area').innerHTML =
2797
2832
  '<div class="docs-placeholder">' +
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-opencode-viewer",
3
- "version": "2.6.42",
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 }));