@wendongfly/zihi 1.1.5 → 1.1.7
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 +244 -27
- package/dist/index.js +108 -108
- package/dist/package.json +1 -1
- package/package.json +1 -1
package/dist/chat.html
CHANGED
|
@@ -224,6 +224,7 @@
|
|
|
224
224
|
.fp-item .fp-name { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
225
225
|
.fp-item .fp-size { color: #8b949e; font-size: 0.7rem; white-space: nowrap; }
|
|
226
226
|
.fp-item .fp-dl { color: #58a6ff; font-size: 0.7rem; text-decoration: none; padding: 0.2rem 0.4rem; }
|
|
227
|
+
.fp-item .fp-del { color: #f85149; font-size: 0.7rem; cursor: pointer; padding: 0.2rem 0.4rem; background: none; border: none; }
|
|
227
228
|
.fp-empty { text-align: center; color: #8b949e; padding: 2rem 0.75rem; font-size: 0.85rem; }
|
|
228
229
|
.fp-drop-zone {
|
|
229
230
|
margin: 0.5rem 0.75rem; padding: 1.5rem; border: 2px dashed #30363d; border-radius: 8px;
|
|
@@ -235,6 +236,19 @@
|
|
|
235
236
|
.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
237
|
.fp-sync-bar .fp-btn { font-size: 0.7rem; padding: 0.25rem 0.5rem; }
|
|
237
238
|
.fp-sync-bar .sync-dir { font-size: 0.68rem; color: #8b949e; flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; }
|
|
239
|
+
.fp-diff-item { display: flex; align-items: center; padding: 0.4rem 0.75rem; gap: 0.4rem; font-size: 0.78rem; color: #e6edf3; }
|
|
240
|
+
.fp-diff-item .badge { font-size: 0.65rem; padding: 0.1rem 0.35rem; border-radius: 4px; font-weight: 600; flex-shrink: 0; }
|
|
241
|
+
.badge-new { background: #238636; color: #fff; }
|
|
242
|
+
.badge-changed { background: #9e6a03; color: #fff; }
|
|
243
|
+
.badge-arrow { color: #8b949e; flex-shrink: 0; font-size: 0.7rem; }
|
|
244
|
+
.fp-diff-name { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
245
|
+
.fp-diff-summary { padding: 0.75rem; text-align: center; color: #8b949e; font-size: 0.8rem; }
|
|
246
|
+
.fp-diff-actions { display: flex; gap: 0.5rem; padding: 0.5rem 0.75rem; flex-shrink: 0; }
|
|
247
|
+
.fp-diff-actions .fp-btn { flex: 1; text-align: center; padding: 0.45rem; }
|
|
248
|
+
.fp-btn-primary { background: #238636; border-color: #2ea04380; }
|
|
249
|
+
.fp-btn-primary:active { background: #2ea043; }
|
|
250
|
+
.fp-btn-warn { background: #9e6a03; border-color: #d2992280; }
|
|
251
|
+
.fp-btn-warn:active { background: #bb8009; }
|
|
238
252
|
|
|
239
253
|
/* ── 底部选择面板 ─────────────────────── */
|
|
240
254
|
.action-sheet { display: none; position: fixed; inset: 0; z-index: 80; }
|
|
@@ -446,16 +460,20 @@
|
|
|
446
460
|
<div class="fp-header">
|
|
447
461
|
<h3 id="fp-title">文件管理</h3>
|
|
448
462
|
<button class="fp-btn" onclick="fpUploadClick()">上传</button>
|
|
449
|
-
<button class="fp-btn" onclick="
|
|
463
|
+
<button class="fp-btn" onclick="fpMkdir()">新建</button>
|
|
450
464
|
<button class="fp-btn" onclick="closeFilePanel()">关闭</button>
|
|
451
465
|
</div>
|
|
452
466
|
<div class="fp-sync-bar" id="fp-sync-bar" style="display:none">
|
|
453
467
|
<span class="sync-dir" id="fp-sync-dir"></span>
|
|
454
468
|
<button class="fp-btn" onclick="fpPickDir()">换目录</button>
|
|
455
|
-
<button class="fp-btn" onclick="fpExitSync()"
|
|
469
|
+
<button class="fp-btn" onclick="fpExitSync()">关闭同步</button>
|
|
456
470
|
</div>
|
|
457
471
|
<div class="fp-path" id="fp-path"></div>
|
|
458
472
|
<div class="fp-list" id="fp-list"></div>
|
|
473
|
+
<div class="fp-diff-actions" id="fp-diff-actions" style="display:none">
|
|
474
|
+
<button class="fp-btn fp-btn-primary" onclick="fpSyncToLocal()">下载到本地</button>
|
|
475
|
+
<button class="fp-btn fp-btn-warn" onclick="fpSyncToServer()">上传到服务器</button>
|
|
476
|
+
</div>
|
|
459
477
|
<div class="fp-progress" id="fp-progress"></div>
|
|
460
478
|
<div class="fp-drop-zone" id="fp-drop">拖拽文件到此处上传,或点击上方按钮</div>
|
|
461
479
|
<input type="file" id="fp-file-input" multiple style="display:none">
|
|
@@ -1777,6 +1795,7 @@
|
|
|
1777
1795
|
window.goBack = function() { window.location.href = '/'; };
|
|
1778
1796
|
// ── 文件管理面板 ──────────────────────────────
|
|
1779
1797
|
let fpCurrentPath = '';
|
|
1798
|
+
var fpQ = 'sessionId=' + encodeURIComponent(SESSION_ID);
|
|
1780
1799
|
window.openFilePanel = function() {
|
|
1781
1800
|
fpCurrentPath = '';
|
|
1782
1801
|
fpLoadFiles();
|
|
@@ -1789,23 +1808,25 @@
|
|
|
1789
1808
|
};
|
|
1790
1809
|
|
|
1791
1810
|
async function fpLoadFiles() {
|
|
1792
|
-
|
|
1811
|
+
var list = document.getElementById('fp-list');
|
|
1793
1812
|
list.innerHTML = '<div class="fp-empty">加载中...</div>';
|
|
1794
1813
|
try {
|
|
1795
|
-
|
|
1796
|
-
|
|
1814
|
+
var r = await fetch('/api/files/list?path=' + encodeURIComponent(fpCurrentPath) + '&' + fpQ);
|
|
1815
|
+
var data = await r.json();
|
|
1797
1816
|
if (data.error) { list.innerHTML = '<div class="fp-empty">' + escHtml(data.error) + '</div>'; return; }
|
|
1798
1817
|
document.getElementById('fp-path').textContent = data.current || '';
|
|
1799
|
-
|
|
1818
|
+
var html = '';
|
|
1800
1819
|
if (data.parent) {
|
|
1801
1820
|
html += '<div class="fp-item" onclick="fpNav(\'' + escHtml(data.parent).replace(/'/g, "\\'") + '\')"><span class="fp-icon">📁</span><span class="fp-name">..</span></div>';
|
|
1802
1821
|
}
|
|
1803
|
-
for (
|
|
1804
|
-
|
|
1822
|
+
for (var i = 0; i < data.entries.length; i++) {
|
|
1823
|
+
var e = data.entries[i];
|
|
1824
|
+
var full = data.current + '/' + e.name;
|
|
1825
|
+
var eFull = escHtml(full).replace(/'/g, "\\'");
|
|
1805
1826
|
if (e.type === 'dir') {
|
|
1806
|
-
html += '<div class="fp-item" onclick="fpNav(\'' +
|
|
1827
|
+
html += '<div class="fp-item"><span class="fp-icon" onclick="fpNav(\'' + eFull + '\')">📁</span><span class="fp-name" onclick="fpNav(\'' + eFull + '\')">' + escHtml(e.name) + '</span><button class="fp-del" onclick="event.stopPropagation();fpDelete(\'' + eFull + '\')">删除</button></div>';
|
|
1807
1828
|
} 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>';
|
|
1829
|
+
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) + '&' + fpQ + '" download title="下载">下载</a><button class="fp-del" onclick="event.stopPropagation();fpDelete(\'' + eFull + '\')">删除</button></div>';
|
|
1809
1830
|
}
|
|
1810
1831
|
}
|
|
1811
1832
|
list.innerHTML = html || '<div class="fp-empty">空目录</div>';
|
|
@@ -1814,7 +1835,37 @@
|
|
|
1814
1835
|
}
|
|
1815
1836
|
}
|
|
1816
1837
|
|
|
1817
|
-
window.fpNav = function(path) { fpCurrentPath = path; fpLoadFiles(); };
|
|
1838
|
+
window.fpNav = function(path) { fpCurrentPath = path; if (syncMode) fpComputeDiff(); else fpLoadFiles(); };
|
|
1839
|
+
|
|
1840
|
+
window.fpMkdir = async function() {
|
|
1841
|
+
var name = prompt('文件夹名称:');
|
|
1842
|
+
if (!name) return;
|
|
1843
|
+
var dirPath = fpCurrentPath ? fpCurrentPath + '/' + name : name;
|
|
1844
|
+
try {
|
|
1845
|
+
var r = await fetch('/api/files/mkdir', {
|
|
1846
|
+
method: 'POST',
|
|
1847
|
+
headers: { 'Content-Type': 'application/json' },
|
|
1848
|
+
body: JSON.stringify({ path: dirPath, sessionId: SESSION_ID }),
|
|
1849
|
+
});
|
|
1850
|
+
var data = await r.json();
|
|
1851
|
+
if (data.ok) fpLoadFiles();
|
|
1852
|
+
else alert('创建失败: ' + (data.error || '未知错误'));
|
|
1853
|
+
} catch (e) { alert('创建失败: ' + e.message); }
|
|
1854
|
+
};
|
|
1855
|
+
|
|
1856
|
+
window.fpDelete = async function(path) {
|
|
1857
|
+
if (!confirm('确定删除?')) return;
|
|
1858
|
+
try {
|
|
1859
|
+
var r = await fetch('/api/files/delete', {
|
|
1860
|
+
method: 'DELETE',
|
|
1861
|
+
headers: { 'Content-Type': 'application/json' },
|
|
1862
|
+
body: JSON.stringify({ path: path, sessionId: SESSION_ID }),
|
|
1863
|
+
});
|
|
1864
|
+
var data = await r.json();
|
|
1865
|
+
if (data.ok) fpLoadFiles();
|
|
1866
|
+
else alert('删除失败: ' + (data.error || '未知错误'));
|
|
1867
|
+
} catch (e) { alert('删除失败: ' + e.message); }
|
|
1868
|
+
};
|
|
1818
1869
|
|
|
1819
1870
|
function fpFormatSize(bytes) {
|
|
1820
1871
|
if (bytes == null) return '';
|
|
@@ -1827,20 +1878,21 @@
|
|
|
1827
1878
|
window.fpUploadClick = function() { document.getElementById('fp-file-input').click(); };
|
|
1828
1879
|
|
|
1829
1880
|
document.getElementById('fp-file-input').addEventListener('change', function(e) {
|
|
1830
|
-
|
|
1881
|
+
var copy = Array.from(e.target.files);
|
|
1831
1882
|
e.target.value = '';
|
|
1883
|
+
if (copy.length) fpUploadFiles(copy);
|
|
1832
1884
|
});
|
|
1833
1885
|
|
|
1834
1886
|
async function fpUploadFiles(files) {
|
|
1835
1887
|
if (!files.length) return;
|
|
1836
|
-
|
|
1888
|
+
var progress = document.getElementById('fp-progress');
|
|
1837
1889
|
progress.style.display = 'block';
|
|
1838
1890
|
progress.textContent = '正在上传 ' + files.length + ' 个文件...';
|
|
1839
1891
|
try {
|
|
1840
|
-
|
|
1841
|
-
for (
|
|
1842
|
-
|
|
1843
|
-
|
|
1892
|
+
var form = new FormData();
|
|
1893
|
+
for (var i = 0; i < files.length; i++) form.append('files', files[i], files[i].webkitRelativePath || files[i].name);
|
|
1894
|
+
var r = await fetch('/api/files/upload?path=' + encodeURIComponent(fpCurrentPath) + '&' + fpQ, { method: 'POST', body: form });
|
|
1895
|
+
var data = await r.json();
|
|
1844
1896
|
if (data.ok) {
|
|
1845
1897
|
progress.textContent = '已上传 ' + data.files.length + ' 个文件';
|
|
1846
1898
|
fpLoadFiles();
|
|
@@ -1860,31 +1912,196 @@
|
|
|
1860
1912
|
fpDropEl.addEventListener('drop', function(e) {
|
|
1861
1913
|
e.preventDefault();
|
|
1862
1914
|
fpDropEl.classList.remove('dragover');
|
|
1863
|
-
if (e.dataTransfer.files.length) fpUploadFiles(e.dataTransfer.files);
|
|
1915
|
+
if (e.dataTransfer.files.length) fpUploadFiles(Array.from(e.dataTransfer.files));
|
|
1864
1916
|
});
|
|
1865
1917
|
|
|
1866
|
-
// 文件同步(File System Access API
|
|
1867
|
-
|
|
1918
|
+
// ── 文件同步(File System Access API)──────────
|
|
1919
|
+
var syncDirHandle = null;
|
|
1920
|
+
var syncMode = false;
|
|
1921
|
+
var syncDiff = { toDownload: [], toUpload: [] };
|
|
1922
|
+
|
|
1868
1923
|
window.fpStartSync = async function() {
|
|
1869
1924
|
if (!window.showDirectoryPicker) { alert('当前浏览器不支持文件夹访问,请使用 Chrome 或 Edge'); return; }
|
|
1925
|
+
if (!syncDirHandle) {
|
|
1926
|
+
await fpPickDirInner();
|
|
1927
|
+
if (!syncDirHandle) return;
|
|
1928
|
+
}
|
|
1929
|
+
syncMode = true;
|
|
1930
|
+
document.getElementById('fp-sync-bar').style.display = 'flex';
|
|
1931
|
+
document.getElementById('fp-drop').style.display = 'none';
|
|
1932
|
+
await fpComputeDiff();
|
|
1933
|
+
};
|
|
1934
|
+
window.fpPickDir = async function() { await fpPickDirInner(); };
|
|
1935
|
+
async function fpPickDirInner() {
|
|
1870
1936
|
try {
|
|
1871
1937
|
syncDirHandle = await window.showDirectoryPicker({ mode: 'readwrite' });
|
|
1872
|
-
document.getElementById('fp-sync-bar').style.display = 'flex';
|
|
1873
1938
|
document.getElementById('fp-sync-dir').textContent = syncDirHandle.name;
|
|
1874
|
-
|
|
1939
|
+
if (syncMode) await fpComputeDiff();
|
|
1875
1940
|
} catch (e) { if (e.name !== 'AbortError') alert('选择文件夹失败: ' + e.message); }
|
|
1876
|
-
}
|
|
1877
|
-
window.fpPickDir = async function() { await window.fpStartSync(); };
|
|
1941
|
+
}
|
|
1878
1942
|
window.fpExitSync = function() {
|
|
1879
|
-
|
|
1943
|
+
syncMode = false;
|
|
1880
1944
|
document.getElementById('fp-sync-bar').style.display = 'none';
|
|
1945
|
+
document.getElementById('fp-diff-actions').style.display = 'none';
|
|
1881
1946
|
document.getElementById('fp-drop').style.display = '';
|
|
1882
1947
|
fpLoadFiles();
|
|
1883
1948
|
};
|
|
1884
1949
|
|
|
1950
|
+
async function walkLocalDir(dirHandle, prefix) {
|
|
1951
|
+
var files = [];
|
|
1952
|
+
for await (var entry of dirHandle.entries()) {
|
|
1953
|
+
var name = entry[0], handle = entry[1];
|
|
1954
|
+
if (name.startsWith('.')) continue;
|
|
1955
|
+
var rel = prefix ? prefix + '/' + name : name;
|
|
1956
|
+
if (handle.kind === 'directory') {
|
|
1957
|
+
files.push.apply(files, await walkLocalDir(handle, rel));
|
|
1958
|
+
} else {
|
|
1959
|
+
var file = await handle.getFile();
|
|
1960
|
+
files.push({ name: rel, size: file.size, handle: handle });
|
|
1961
|
+
}
|
|
1962
|
+
}
|
|
1963
|
+
return files;
|
|
1964
|
+
}
|
|
1965
|
+
|
|
1966
|
+
async function hashFile(file) {
|
|
1967
|
+
var buf = await file.arrayBuffer();
|
|
1968
|
+
var hash = await crypto.subtle.digest('SHA-256', buf);
|
|
1969
|
+
return Array.from(new Uint8Array(hash)).map(function(b) { return b.toString(16).padStart(2, '0'); }).join('');
|
|
1970
|
+
}
|
|
1971
|
+
|
|
1972
|
+
async function fpComputeDiff() {
|
|
1973
|
+
var list = document.getElementById('fp-list');
|
|
1974
|
+
list.innerHTML = '<div class="fp-empty">正在比对文件...</div>';
|
|
1975
|
+
var progress = document.getElementById('fp-progress');
|
|
1976
|
+
progress.style.display = 'block';
|
|
1977
|
+
try {
|
|
1978
|
+
progress.textContent = '获取服务器文件清单...';
|
|
1979
|
+
var r = await fetch('/api/files/list?path=' + encodeURIComponent(fpCurrentPath) + '&' + fpQ + '&recursive=1');
|
|
1980
|
+
var data = await r.json();
|
|
1981
|
+
if (data.error) throw new Error(data.error);
|
|
1982
|
+
|
|
1983
|
+
progress.textContent = '扫描本地文件...';
|
|
1984
|
+
var localFiles = await walkLocalDir(syncDirHandle, '');
|
|
1985
|
+
var serverFiles = (data.entries || []).filter(function(e) { return e.type === 'file'; });
|
|
1986
|
+
var serverMap = new Map(serverFiles.map(function(f) { return [f.name, f]; }));
|
|
1987
|
+
var localMap = new Map(localFiles.map(function(f) { return [f.name, f]; }));
|
|
1988
|
+
|
|
1989
|
+
var toDownload = [], toUpload = [];
|
|
1990
|
+
var checked = 0, total = serverMap.size + localMap.size;
|
|
1991
|
+
|
|
1992
|
+
for (var entry of serverMap) {
|
|
1993
|
+
var sName = entry[0], sf = entry[1];
|
|
1994
|
+
if (++checked % 20 === 0) progress.textContent = '比对中... ' + checked + '/' + total;
|
|
1995
|
+
var lf = localMap.get(sName);
|
|
1996
|
+
if (!lf) {
|
|
1997
|
+
toDownload.push({ name: sName, size: sf.size, reason: 'new' });
|
|
1998
|
+
} else if (lf.size !== sf.size) {
|
|
1999
|
+
toDownload.push({ name: sName, size: sf.size, reason: 'changed' });
|
|
2000
|
+
}
|
|
2001
|
+
}
|
|
2002
|
+
for (var entry2 of localMap) {
|
|
2003
|
+
var lName = entry2[0], lf2 = entry2[1];
|
|
2004
|
+
if (++checked % 20 === 0) progress.textContent = '比对中... ' + checked + '/' + total;
|
|
2005
|
+
var sf2 = serverMap.get(lName);
|
|
2006
|
+
if (!sf2) {
|
|
2007
|
+
toUpload.push({ name: lName, size: lf2.size, reason: 'new', handle: lf2.handle });
|
|
2008
|
+
} else if (lf2.size !== sf2.size) {
|
|
2009
|
+
toUpload.push({ name: lName, size: lf2.size, reason: 'changed', handle: lf2.handle });
|
|
2010
|
+
}
|
|
2011
|
+
}
|
|
2012
|
+
|
|
2013
|
+
syncDiff = { toDownload: toDownload, toUpload: toUpload };
|
|
2014
|
+
fpRenderDiff();
|
|
2015
|
+
} catch (e) {
|
|
2016
|
+
list.innerHTML = '<div class="fp-empty">比对失败: ' + escHtml(e.message) + '</div>';
|
|
2017
|
+
}
|
|
2018
|
+
progress.style.display = 'none';
|
|
2019
|
+
}
|
|
2020
|
+
|
|
2021
|
+
function fpRenderDiff() {
|
|
2022
|
+
var td = syncDiff.toDownload, tu = syncDiff.toUpload;
|
|
2023
|
+
var list = document.getElementById('fp-list');
|
|
2024
|
+
if (!td.length && !tu.length) {
|
|
2025
|
+
list.innerHTML = '<div class="fp-diff-summary">文件已同步,无差异</div>';
|
|
2026
|
+
document.getElementById('fp-diff-actions').style.display = 'none';
|
|
2027
|
+
return;
|
|
2028
|
+
}
|
|
2029
|
+
var html = '<div class="fp-diff-summary">';
|
|
2030
|
+
if (td.length) html += td.length + ' 个文件需下载到本地';
|
|
2031
|
+
if (td.length && tu.length) html += ',';
|
|
2032
|
+
if (tu.length) html += tu.length + ' 个文件需上传到服务器';
|
|
2033
|
+
html += '</div>';
|
|
2034
|
+
for (var i = 0; i < td.length; i++) {
|
|
2035
|
+
var f = td[i];
|
|
2036
|
+
html += '<div class="fp-diff-item"><span class="badge ' + (f.reason === 'new' ? 'badge-new' : 'badge-changed') + '">' + (f.reason === 'new' ? '新' : '改') + '</span><span class="badge-arrow">↓</span><span class="fp-diff-name">' + escHtml(f.name) + '</span><span class="fp-size">' + fpFormatSize(f.size) + '</span></div>';
|
|
2037
|
+
}
|
|
2038
|
+
for (var j = 0; j < tu.length; j++) {
|
|
2039
|
+
var g = tu[j];
|
|
2040
|
+
html += '<div class="fp-diff-item"><span class="badge ' + (g.reason === 'new' ? 'badge-new' : 'badge-changed') + '">' + (g.reason === 'new' ? '新' : '改') + '</span><span class="badge-arrow">↑</span><span class="fp-diff-name">' + escHtml(g.name) + '</span><span class="fp-size">' + fpFormatSize(g.size) + '</span></div>';
|
|
2041
|
+
}
|
|
2042
|
+
list.innerHTML = html;
|
|
2043
|
+
document.getElementById('fp-diff-actions').style.display = 'flex';
|
|
2044
|
+
}
|
|
2045
|
+
|
|
2046
|
+
window.fpSyncToLocal = async function() {
|
|
2047
|
+
var toDownload = syncDiff.toDownload;
|
|
2048
|
+
if (!toDownload.length) return;
|
|
2049
|
+
var progress = document.getElementById('fp-progress');
|
|
2050
|
+
progress.style.display = 'block';
|
|
2051
|
+
var done = 0;
|
|
2052
|
+
for (var i = 0; i < toDownload.length; i++) {
|
|
2053
|
+
var f = toDownload[i];
|
|
2054
|
+
progress.textContent = '下载中 ' + (++done) + '/' + toDownload.length + ': ' + f.name;
|
|
2055
|
+
try {
|
|
2056
|
+
var relPath = fpCurrentPath ? fpCurrentPath + '/' + f.name : f.name;
|
|
2057
|
+
var r = await fetch('/api/files/download?path=' + encodeURIComponent(relPath) + '&' + fpQ);
|
|
2058
|
+
if (!r.ok) continue;
|
|
2059
|
+
var blob = await r.blob();
|
|
2060
|
+
var parts = f.name.split('/');
|
|
2061
|
+
var dir = syncDirHandle;
|
|
2062
|
+
for (var p = 0; p < parts.length - 1; p++) {
|
|
2063
|
+
dir = await dir.getDirectoryHandle(parts[p], { create: true });
|
|
2064
|
+
}
|
|
2065
|
+
var fh = await dir.getFileHandle(parts[parts.length - 1], { create: true });
|
|
2066
|
+
var w = await fh.createWritable();
|
|
2067
|
+
await w.write(blob);
|
|
2068
|
+
await w.close();
|
|
2069
|
+
} catch (e) { console.error('同步下载失败 ' + f.name + ':', e); }
|
|
2070
|
+
}
|
|
2071
|
+
progress.textContent = '已下载 ' + done + ' 个文件到本地';
|
|
2072
|
+
setTimeout(function() { progress.style.display = 'none'; }, 3000);
|
|
2073
|
+
await fpComputeDiff();
|
|
2074
|
+
};
|
|
2075
|
+
|
|
2076
|
+
window.fpSyncToServer = async function() {
|
|
2077
|
+
var toUpload = syncDiff.toUpload;
|
|
2078
|
+
if (!toUpload.length) return;
|
|
2079
|
+
var progress = document.getElementById('fp-progress');
|
|
2080
|
+
progress.style.display = 'block';
|
|
2081
|
+
var BATCH = 20, done = 0;
|
|
2082
|
+
for (var i = 0; i < toUpload.length; i += BATCH) {
|
|
2083
|
+
var batch = toUpload.slice(i, i + BATCH);
|
|
2084
|
+
progress.textContent = '上传中 ' + done + '/' + toUpload.length + '...';
|
|
2085
|
+
var form = new FormData();
|
|
2086
|
+
for (var b = 0; b < batch.length; b++) {
|
|
2087
|
+
try { form.append('files', await batch[b].handle.getFile(), batch[b].name); }
|
|
2088
|
+
catch (e) { console.error('读取失败 ' + batch[b].name + ':', e); }
|
|
2089
|
+
}
|
|
2090
|
+
try {
|
|
2091
|
+
await fetch('/api/files/upload?path=' + encodeURIComponent(fpCurrentPath) + '&' + fpQ, { method: 'POST', body: form });
|
|
2092
|
+
} catch (e) { console.error('上传批次失败:', e); }
|
|
2093
|
+
done += batch.length;
|
|
2094
|
+
}
|
|
2095
|
+
progress.textContent = '已上传 ' + done + ' 个文件到服务器';
|
|
2096
|
+
setTimeout(function() { progress.style.display = 'none'; }, 3000);
|
|
2097
|
+
await fpComputeDiff();
|
|
2098
|
+
};
|
|
2099
|
+
|
|
1885
2100
|
// 监听文件变更
|
|
1886
|
-
socket.on('files:changed', function() {
|
|
1887
|
-
if (document.getElementById('file-panel').classList.contains('open'))
|
|
2101
|
+
socket.on('files:changed', function(data) {
|
|
2102
|
+
if (data.sessionId === SESSION_ID && document.getElementById('file-panel').classList.contains('open')) {
|
|
2103
|
+
if (syncMode) fpComputeDiff(); else fpLoadFiles();
|
|
2104
|
+
}
|
|
1888
2105
|
});
|
|
1889
2106
|
function scrollToBottom() { requestAnimationFrame(() => { chatArea.scrollTop = chatArea.scrollHeight; }); }
|
|
1890
2107
|
function trimMessages() {
|