@wendongfly/zihi 1.1.3 → 1.1.5

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/dist/chat.html CHANGED
@@ -203,6 +203,39 @@
203
203
  .setting-toggle input[type=checkbox]::after { content: ''; position: absolute; top: 2px; left: 2px; width: 16px; height: 16px; background: #fff; border-radius: 50%; transition: left 0.2s; }
204
204
  .setting-toggle input[type=checkbox]:checked::after { left: 18px; }
205
205
 
206
+ /* ── 文件管理面板 ─────────────────────── */
207
+ #file-panel-backdrop { position: fixed; inset: 0; background: rgba(0,0,0,0.5); z-index: 62; display: none; }
208
+ #file-panel-backdrop.open { display: block; }
209
+ #file-panel {
210
+ position: fixed; top: 0; right: -340px; width: 320px; max-width: 90vw; height: 100%;
211
+ background: #161b22; border-left: 1px solid #30363d; z-index: 63;
212
+ transition: right 0.25s ease; display: flex; flex-direction: column;
213
+ }
214
+ #file-panel.open { right: 0; }
215
+ .fp-header { display: flex; align-items: center; padding: 0.75rem; border-bottom: 1px solid #30363d; gap: 0.5rem; flex-shrink: 0; }
216
+ .fp-header h3 { flex: 1; font-size: 0.9rem; margin: 0; color: #e6edf3; }
217
+ .fp-btn { background: #21262d; color: #e6edf3; border: 1px solid #30363d; border-radius: 6px; padding: 0.3rem 0.6rem; font-size: 0.75rem; cursor: pointer; }
218
+ .fp-btn:active { background: #30363d; }
219
+ .fp-path { padding: 0.4rem 0.75rem; font-size: 0.7rem; color: #8b949e; border-bottom: 1px solid #21262d; word-break: break-all; flex-shrink: 0; }
220
+ .fp-list { flex: 1; overflow-y: auto; padding: 0.25rem 0; }
221
+ .fp-item { display: flex; align-items: center; padding: 0.5rem 0.75rem; gap: 0.5rem; cursor: pointer; font-size: 0.82rem; color: #e6edf3; }
222
+ .fp-item:active { background: #21262d; }
223
+ .fp-item .fp-icon { width: 1.2rem; text-align: center; flex-shrink: 0; }
224
+ .fp-item .fp-name { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
225
+ .fp-item .fp-size { color: #8b949e; font-size: 0.7rem; white-space: nowrap; }
226
+ .fp-item .fp-dl { color: #58a6ff; font-size: 0.7rem; text-decoration: none; padding: 0.2rem 0.4rem; }
227
+ .fp-empty { text-align: center; color: #8b949e; padding: 2rem 0.75rem; font-size: 0.85rem; }
228
+ .fp-drop-zone {
229
+ margin: 0.5rem 0.75rem; padding: 1.5rem; border: 2px dashed #30363d; border-radius: 8px;
230
+ text-align: center; color: #8b949e; font-size: 0.8rem; flex-shrink: 0;
231
+ transition: border-color 0.2s, background 0.2s;
232
+ }
233
+ .fp-drop-zone.dragover { border-color: #58a6ff; background: rgba(88,166,255,0.08); }
234
+ .fp-progress { padding: 0.4rem 0.75rem; font-size: 0.75rem; color: #d29922; flex-shrink: 0; display: none; }
235
+ .fp-sync-bar { display: flex; gap: 0.4rem; padding: 0.5rem 0.75rem; border-bottom: 1px solid #21262d; flex-shrink: 0; align-items: center; flex-wrap: wrap; }
236
+ .fp-sync-bar .fp-btn { font-size: 0.7rem; padding: 0.25rem 0.5rem; }
237
+ .fp-sync-bar .sync-dir { font-size: 0.68rem; color: #8b949e; flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; }
238
+
206
239
  /* ── 底部选择面板 ─────────────────────── */
207
240
  .action-sheet { display: none; position: fixed; inset: 0; z-index: 80; }
208
241
  .action-sheet.open { display: flex; align-items: flex-end; justify-content: center; }
@@ -243,7 +276,7 @@
243
276
  <div id="session-title">加载中...</div>
244
277
  <div id="session-cwd"></div>
245
278
  </div>
246
- <button class="top-btn" onclick="openFiles()" title="文件" style="font-size:0.7rem;color:var(--muted)">文件</button>
279
+ <button class="top-btn" onclick="openFilePanel()" title="文件" style="font-size:0.7rem;color:var(--muted)">文件</button>
247
280
  <button class="top-btn" onclick="showUsage()" title="用量" style="font-size:0.7rem;color:var(--muted)">用量</button>
248
281
  <button class="top-btn" onclick="openSettings()" title="设置">
249
282
  <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.65 1.65 0 00.33 1.82l.06.06a2 2 0 01-2.83 2.83l-.06-.06a1.65 1.65 0 00-1.82-.33 1.65 1.65 0 00-1 1.51V21a2 2 0 01-4 0v-.09A1.65 1.65 0 009 19.4a1.65 1.65 0 00-1.82.33l-.06.06a2 2 0 01-2.83-2.83l.06-.06A1.65 1.65 0 004.68 15a1.65 1.65 0 00-1.51-1H3a2 2 0 010-4h.09A1.65 1.65 0 004.6 9a1.65 1.65 0 00-.33-1.82l-.06-.06a2 2 0 012.83-2.83l.06.06A1.65 1.65 0 009 4.68a1.65 1.65 0 001-1.51V3a2 2 0 014 0v.09a1.65 1.65 0 001 1.51 1.65 1.65 0 001.82-.33l.06-.06a2 2 0 012.83 2.83l-.06.06A1.65 1.65 0 0019.4 9a1.65 1.65 0 001.51 1H21a2 2 0 010 4h-.09a1.65 1.65 0 00-1.51 1z"/></svg>
@@ -407,6 +440,27 @@
407
440
  </div>
408
441
  </div>
409
442
 
443
+ <!-- 文件管理面板 -->
444
+ <div id="file-panel-backdrop" onclick="closeFilePanel()"></div>
445
+ <div id="file-panel">
446
+ <div class="fp-header">
447
+ <h3 id="fp-title">文件管理</h3>
448
+ <button class="fp-btn" onclick="fpUploadClick()">上传</button>
449
+ <button class="fp-btn" onclick="fpStartSync()">同步</button>
450
+ <button class="fp-btn" onclick="closeFilePanel()">关闭</button>
451
+ </div>
452
+ <div class="fp-sync-bar" id="fp-sync-bar" style="display:none">
453
+ <span class="sync-dir" id="fp-sync-dir"></span>
454
+ <button class="fp-btn" onclick="fpPickDir()">换目录</button>
455
+ <button class="fp-btn" onclick="fpExitSync()">退出同步</button>
456
+ </div>
457
+ <div class="fp-path" id="fp-path"></div>
458
+ <div class="fp-list" id="fp-list"></div>
459
+ <div class="fp-progress" id="fp-progress"></div>
460
+ <div class="fp-drop-zone" id="fp-drop">拖拽文件到此处上传,或点击上方按钮</div>
461
+ <input type="file" id="fp-file-input" multiple style="display:none">
462
+ </div>
463
+
410
464
  <div id="status-overlay">连接中...</div>
411
465
 
412
466
  <script>
@@ -1721,7 +1775,117 @@
1721
1775
  // ── 辅助 ──────────────────────────────────────
1722
1776
  function updateViewers(count) { document.getElementById('viewer-count').textContent = count > 1 ? `${count} 人在线` : ''; }
1723
1777
  window.goBack = function() { window.location.href = '/'; };
1724
- window.openFiles = function() { window.open('/files?session=' + SESSION_ID, '_blank'); };
1778
+ // ── 文件管理面板 ──────────────────────────────
1779
+ let fpCurrentPath = '';
1780
+ window.openFilePanel = function() {
1781
+ fpCurrentPath = '';
1782
+ fpLoadFiles();
1783
+ document.getElementById('file-panel').classList.add('open');
1784
+ document.getElementById('file-panel-backdrop').classList.add('open');
1785
+ };
1786
+ window.closeFilePanel = function() {
1787
+ document.getElementById('file-panel').classList.remove('open');
1788
+ document.getElementById('file-panel-backdrop').classList.remove('open');
1789
+ };
1790
+
1791
+ async function fpLoadFiles() {
1792
+ const list = document.getElementById('fp-list');
1793
+ list.innerHTML = '<div class="fp-empty">加载中...</div>';
1794
+ try {
1795
+ const r = await fetch('/api/files/list?path=' + encodeURIComponent(fpCurrentPath));
1796
+ const data = await r.json();
1797
+ if (data.error) { list.innerHTML = '<div class="fp-empty">' + escHtml(data.error) + '</div>'; return; }
1798
+ document.getElementById('fp-path').textContent = data.current || '';
1799
+ let html = '';
1800
+ if (data.parent) {
1801
+ html += '<div class="fp-item" onclick="fpNav(\'' + escHtml(data.parent).replace(/'/g, "\\'") + '\')"><span class="fp-icon">📁</span><span class="fp-name">..</span></div>';
1802
+ }
1803
+ for (const e of data.entries) {
1804
+ const full = data.current + '/' + e.name;
1805
+ if (e.type === 'dir') {
1806
+ html += '<div class="fp-item" onclick="fpNav(\'' + escHtml(full).replace(/'/g, "\\'") + '\')"><span class="fp-icon">📁</span><span class="fp-name">' + escHtml(e.name) + '</span></div>';
1807
+ } else {
1808
+ html += '<div class="fp-item"><span class="fp-icon">📄</span><span class="fp-name">' + escHtml(e.name) + '</span><span class="fp-size">' + fpFormatSize(e.size) + '</span><a class="fp-dl" href="/api/files/download?path=' + encodeURIComponent(full) + '" download title="下载">下载</a></div>';
1809
+ }
1810
+ }
1811
+ list.innerHTML = html || '<div class="fp-empty">空目录</div>';
1812
+ } catch (e) {
1813
+ list.innerHTML = '<div class="fp-empty">加载失败: ' + escHtml(e.message) + '</div>';
1814
+ }
1815
+ }
1816
+
1817
+ window.fpNav = function(path) { fpCurrentPath = path; fpLoadFiles(); };
1818
+
1819
+ function fpFormatSize(bytes) {
1820
+ if (bytes == null) return '';
1821
+ if (bytes < 1024) return bytes + ' B';
1822
+ if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + ' KB';
1823
+ if (bytes < 1024 * 1024 * 1024) return (bytes / 1024 / 1024).toFixed(1) + ' MB';
1824
+ return (bytes / 1024 / 1024 / 1024).toFixed(1) + ' GB';
1825
+ }
1826
+
1827
+ window.fpUploadClick = function() { document.getElementById('fp-file-input').click(); };
1828
+
1829
+ document.getElementById('fp-file-input').addEventListener('change', function(e) {
1830
+ if (e.target.files.length) fpUploadFiles(e.target.files);
1831
+ e.target.value = '';
1832
+ });
1833
+
1834
+ async function fpUploadFiles(files) {
1835
+ if (!files.length) return;
1836
+ const progress = document.getElementById('fp-progress');
1837
+ progress.style.display = 'block';
1838
+ progress.textContent = '正在上传 ' + files.length + ' 个文件...';
1839
+ try {
1840
+ const form = new FormData();
1841
+ for (const file of files) form.append('files', file, file.webkitRelativePath || file.name);
1842
+ const r = await fetch('/api/files/upload?path=' + encodeURIComponent(fpCurrentPath), { method: 'POST', body: form });
1843
+ const data = await r.json();
1844
+ if (data.ok) {
1845
+ progress.textContent = '已上传 ' + data.files.length + ' 个文件';
1846
+ fpLoadFiles();
1847
+ } else {
1848
+ progress.textContent = '上传失败: ' + (data.error || '未知错误');
1849
+ }
1850
+ } catch (e) {
1851
+ progress.textContent = '上传出错: ' + e.message;
1852
+ }
1853
+ setTimeout(function() { progress.style.display = 'none'; }, 3000);
1854
+ }
1855
+
1856
+ // 拖拽上传
1857
+ var fpDropEl = document.getElementById('fp-drop');
1858
+ fpDropEl.addEventListener('dragover', function(e) { e.preventDefault(); fpDropEl.classList.add('dragover'); });
1859
+ fpDropEl.addEventListener('dragleave', function() { fpDropEl.classList.remove('dragover'); });
1860
+ fpDropEl.addEventListener('drop', function(e) {
1861
+ e.preventDefault();
1862
+ fpDropEl.classList.remove('dragover');
1863
+ if (e.dataTransfer.files.length) fpUploadFiles(e.dataTransfer.files);
1864
+ });
1865
+
1866
+ // 文件同步(File System Access API)
1867
+ let syncDirHandle = null;
1868
+ window.fpStartSync = async function() {
1869
+ if (!window.showDirectoryPicker) { alert('当前浏览器不支持文件夹访问,请使用 Chrome 或 Edge'); return; }
1870
+ try {
1871
+ syncDirHandle = await window.showDirectoryPicker({ mode: 'readwrite' });
1872
+ document.getElementById('fp-sync-bar').style.display = 'flex';
1873
+ document.getElementById('fp-sync-dir').textContent = syncDirHandle.name;
1874
+ document.getElementById('fp-drop').style.display = 'none';
1875
+ } catch (e) { if (e.name !== 'AbortError') alert('选择文件夹失败: ' + e.message); }
1876
+ };
1877
+ window.fpPickDir = async function() { await window.fpStartSync(); };
1878
+ window.fpExitSync = function() {
1879
+ syncDirHandle = null;
1880
+ document.getElementById('fp-sync-bar').style.display = 'none';
1881
+ document.getElementById('fp-drop').style.display = '';
1882
+ fpLoadFiles();
1883
+ };
1884
+
1885
+ // 监听文件变更
1886
+ socket.on('files:changed', function() {
1887
+ if (document.getElementById('file-panel').classList.contains('open')) fpLoadFiles();
1888
+ });
1725
1889
  function scrollToBottom() { requestAnimationFrame(() => { chatArea.scrollTop = chatArea.scrollHeight; }); }
1726
1890
  function trimMessages() {
1727
1891
  const msgs = chatArea.querySelectorAll('.msg');
package/dist/files.html CHANGED
@@ -576,8 +576,9 @@ function closeUploadSheet() {
576
576
 
577
577
  function handleFiles(files) {
578
578
  if (!files.length) return;
579
+ const copy = Array.from(files);
579
580
  closeUploadSheet();
580
- uploadFiles(files);
581
+ uploadFiles(copy);
581
582
  }
582
583
 
583
584
  async function uploadFiles(files) {
package/dist/package.json CHANGED
@@ -1 +1 @@
1
- {"type":"module","version":"1.1.2"}
1
+ {"type":"module","version":"1.1.5"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wendongfly/zihi",
3
- "version": "1.1.3",
3
+ "version": "1.1.5",
4
4
  "description": "AI Agent terminal sharing with interactive permission approval via Claude Agent SDK",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",