@xcanwin/manyoyo 5.7.3 → 5.7.4
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/lib/web/frontend/app.css +169 -0
- package/lib/web/frontend/app.html +32 -3
- package/lib/web/frontend/app.js +412 -102
- package/lib/web/server.js +457 -161
- package/package.json +1 -1
package/lib/web/frontend/app.js
CHANGED
|
@@ -63,6 +63,17 @@
|
|
|
63
63
|
createRuns: {},
|
|
64
64
|
sessionNodeMap: new Map(),
|
|
65
65
|
sessionRenderMode: 'empty',
|
|
66
|
+
directoryPicker: {
|
|
67
|
+
open: false,
|
|
68
|
+
loading: false,
|
|
69
|
+
mode: '',
|
|
70
|
+
title: '',
|
|
71
|
+
tip: '',
|
|
72
|
+
currentPath: '',
|
|
73
|
+
basePath: '',
|
|
74
|
+
entries: [],
|
|
75
|
+
error: ''
|
|
76
|
+
},
|
|
66
77
|
messageRequestId: 0,
|
|
67
78
|
agentRun: {
|
|
68
79
|
active: false,
|
|
@@ -121,6 +132,8 @@
|
|
|
121
132
|
const createContainerName = document.getElementById('createContainerName');
|
|
122
133
|
const createHostPath = document.getElementById('createHostPath');
|
|
123
134
|
const createContainerPath = document.getElementById('createContainerPath');
|
|
135
|
+
const pickHostPathBtn = document.getElementById('pickHostPathBtn');
|
|
136
|
+
const pickContainerPathBtn = document.getElementById('pickContainerPathBtn');
|
|
124
137
|
const createImageName = document.getElementById('createImageName');
|
|
125
138
|
const createImageVersion = document.getElementById('createImageVersion');
|
|
126
139
|
const createContainerMode = document.getElementById('createContainerMode');
|
|
@@ -132,6 +145,15 @@
|
|
|
132
145
|
const createEnv = document.getElementById('createEnv');
|
|
133
146
|
const createEnvFile = document.getElementById('createEnvFile');
|
|
134
147
|
const createVolumes = document.getElementById('createVolumes');
|
|
148
|
+
const directoryPickerModal = document.getElementById('directoryPickerModal');
|
|
149
|
+
const directoryPickerTitle = document.getElementById('directoryPickerTitle');
|
|
150
|
+
const directoryPickerTip = document.getElementById('directoryPickerTip');
|
|
151
|
+
const directoryPickerCurrent = document.getElementById('directoryPickerCurrent');
|
|
152
|
+
const directoryPickerList = document.getElementById('directoryPickerList');
|
|
153
|
+
const directoryPickerError = document.getElementById('directoryPickerError');
|
|
154
|
+
const directoryPickerCancelBtn = document.getElementById('directoryPickerCancelBtn');
|
|
155
|
+
const directoryPickerUpBtn = document.getElementById('directoryPickerUpBtn');
|
|
156
|
+
const directoryPickerSelectBtn = document.getElementById('directoryPickerSelectBtn');
|
|
135
157
|
const activeTitle = document.getElementById('activeTitle');
|
|
136
158
|
const activeMeta = document.getElementById('activeMeta');
|
|
137
159
|
const activityCommandBtn = document.getElementById('activityCommandBtn');
|
|
@@ -437,6 +459,32 @@
|
|
|
437
459
|
});
|
|
438
460
|
}
|
|
439
461
|
|
|
462
|
+
function normalizeSlashPath(value) {
|
|
463
|
+
return String(value || '').replace(/\\/g, '/');
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
function isChildPath(basePath, targetPath) {
|
|
467
|
+
const normalizedBase = normalizeSlashPath(basePath).replace(/\/+$/, '');
|
|
468
|
+
const normalizedTarget = normalizeSlashPath(targetPath).replace(/\/+$/, '');
|
|
469
|
+
if (!normalizedBase) {
|
|
470
|
+
return false;
|
|
471
|
+
}
|
|
472
|
+
return normalizedTarget === normalizedBase || normalizedTarget.startsWith(normalizedBase + '/');
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
function buildContainerPathFromHostSelection(baseHostPath, baseContainerPath, selectedHostPath) {
|
|
476
|
+
const normalizedBaseHost = normalizeSlashPath(baseHostPath).replace(/\/+$/, '');
|
|
477
|
+
const normalizedContainer = normalizeSlashPath(baseContainerPath).replace(/\/+$/, '') || '/workspace';
|
|
478
|
+
const normalizedSelected = normalizeSlashPath(selectedHostPath).replace(/\/+$/, '');
|
|
479
|
+
if (!normalizedBaseHost || !isChildPath(normalizedBaseHost, normalizedSelected)) {
|
|
480
|
+
return normalizedContainer;
|
|
481
|
+
}
|
|
482
|
+
const relative = normalizedSelected === normalizedBaseHost
|
|
483
|
+
? ''
|
|
484
|
+
: normalizedSelected.slice(normalizedBaseHost.length + 1);
|
|
485
|
+
return relative ? `${normalizedContainer}/${relative}`.replace(/\/+/g, '/') : normalizedContainer;
|
|
486
|
+
}
|
|
487
|
+
|
|
440
488
|
function setModalVisible(modalNode, visible) {
|
|
441
489
|
if (!modalNode) return;
|
|
442
490
|
modalNode.hidden = !visible;
|
|
@@ -467,6 +515,18 @@
|
|
|
467
515
|
configError.textContent = text;
|
|
468
516
|
}
|
|
469
517
|
|
|
518
|
+
function showDirectoryPickerError(message) {
|
|
519
|
+
if (!directoryPickerError) return;
|
|
520
|
+
const text = String(message || '').trim();
|
|
521
|
+
if (!text) {
|
|
522
|
+
directoryPickerError.hidden = true;
|
|
523
|
+
directoryPickerError.textContent = '';
|
|
524
|
+
return;
|
|
525
|
+
}
|
|
526
|
+
directoryPickerError.hidden = false;
|
|
527
|
+
directoryPickerError.textContent = text;
|
|
528
|
+
}
|
|
529
|
+
|
|
470
530
|
function envMapToText(envMap) {
|
|
471
531
|
if (!envMap || typeof envMap !== 'object') {
|
|
472
532
|
return '';
|
|
@@ -794,7 +854,8 @@
|
|
|
794
854
|
const status = sessionStatusInfo(session.status);
|
|
795
855
|
const messageCount = safeMessageCount(session.messageCount);
|
|
796
856
|
const updatedAt = formatDateTime(session.updatedAt) || '暂无更新';
|
|
797
|
-
|
|
857
|
+
const containerName = session.containerName || '未绑定容器';
|
|
858
|
+
return `${containerName} · ${status.label} · ${messageCount} 条对话 · ${updatedAt}`;
|
|
798
859
|
}
|
|
799
860
|
|
|
800
861
|
function buildMessageMetaLines(message) {
|
|
@@ -1309,7 +1370,8 @@
|
|
|
1309
1370
|
if (detailSummary) {
|
|
1310
1371
|
detailSummary.innerHTML = '';
|
|
1311
1372
|
renderKeyValueCard(detailSummary, '会话概览', [
|
|
1312
|
-
{ label: '
|
|
1373
|
+
{ label: 'AGENT', value: detail.agentName || detail.name || state.active },
|
|
1374
|
+
{ label: '容器', value: detail.containerName || '—' },
|
|
1313
1375
|
{ label: '状态', value: status.label, tone: status.tone },
|
|
1314
1376
|
{ label: '镜像', value: detail.image || applied.imageName || '—' },
|
|
1315
1377
|
{ label: '最近更新', value: updatedText },
|
|
@@ -1332,7 +1394,8 @@
|
|
|
1332
1394
|
if (configSummary) {
|
|
1333
1395
|
configSummary.innerHTML = '';
|
|
1334
1396
|
renderKeyValueCard(configSummary, '基础配置', [
|
|
1335
|
-
{ label: '
|
|
1397
|
+
{ label: 'AGENT', value: detail.agentName || '—' },
|
|
1398
|
+
{ label: 'containerName', value: applied.containerName || detail.containerName || '—' },
|
|
1336
1399
|
{ label: 'imageName', value: applied.imageName || detail.image || '—' },
|
|
1337
1400
|
{ label: 'imageVersion', value: applied.imageVersion || '—' },
|
|
1338
1401
|
{ label: 'containerMode', value: applied.containerMode || 'default' }
|
|
@@ -1406,8 +1469,9 @@
|
|
|
1406
1469
|
commandInput.value = '';
|
|
1407
1470
|
}
|
|
1408
1471
|
} else {
|
|
1409
|
-
|
|
1410
|
-
|
|
1472
|
+
const activeSession = getActiveSession();
|
|
1473
|
+
activeTitle.textContent = activeSession && activeSession.agentName ? activeSession.agentName : state.active;
|
|
1474
|
+
activeMeta.textContent = buildActiveMeta(activeSession);
|
|
1411
1475
|
}
|
|
1412
1476
|
|
|
1413
1477
|
const activityTab = state.activeTab === 'activity';
|
|
@@ -1522,6 +1586,13 @@
|
|
|
1522
1586
|
if (createModal) {
|
|
1523
1587
|
createModal.hidden = !state.createModalOpen;
|
|
1524
1588
|
}
|
|
1589
|
+
if (directoryPickerModal) {
|
|
1590
|
+
directoryPickerModal.hidden = !state.directoryPicker.open;
|
|
1591
|
+
}
|
|
1592
|
+
document.body.classList.toggle(
|
|
1593
|
+
'modal-open',
|
|
1594
|
+
state.configModalOpen || state.createModalOpen || state.directoryPicker.open
|
|
1595
|
+
);
|
|
1525
1596
|
if (!state.active) {
|
|
1526
1597
|
sendState.textContent = '未选择会话';
|
|
1527
1598
|
} else if (agentMode && !agentEnabled) {
|
|
@@ -1718,6 +1789,7 @@
|
|
|
1718
1789
|
function closeCreateModal() {
|
|
1719
1790
|
state.createModalOpen = false;
|
|
1720
1791
|
setModalVisible(createModal, false);
|
|
1792
|
+
closeDirectoryPicker();
|
|
1721
1793
|
showCreateError('');
|
|
1722
1794
|
}
|
|
1723
1795
|
|
|
@@ -1726,57 +1798,159 @@
|
|
|
1726
1798
|
showCreateError('');
|
|
1727
1799
|
}
|
|
1728
1800
|
|
|
1729
|
-
function
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1801
|
+
function renderDirectoryPicker() {
|
|
1802
|
+
if (!directoryPickerModal) return;
|
|
1803
|
+
const picker = state.directoryPicker;
|
|
1804
|
+
setModalVisible(directoryPickerModal, picker.open);
|
|
1805
|
+
if (directoryPickerTitle) {
|
|
1806
|
+
directoryPickerTitle.textContent = picker.title || '选择目录';
|
|
1807
|
+
}
|
|
1808
|
+
if (directoryPickerTip) {
|
|
1809
|
+
directoryPickerTip.textContent = picker.tip || '';
|
|
1810
|
+
}
|
|
1811
|
+
if (directoryPickerCurrent) {
|
|
1812
|
+
directoryPickerCurrent.textContent = picker.currentPath || '未选择目录';
|
|
1813
|
+
}
|
|
1814
|
+
showDirectoryPickerError(picker.error);
|
|
1815
|
+
if (directoryPickerUpBtn) {
|
|
1816
|
+
directoryPickerUpBtn.disabled = picker.loading || !picker.currentPath || !picker.parentPath;
|
|
1817
|
+
}
|
|
1818
|
+
if (directoryPickerSelectBtn) {
|
|
1819
|
+
directoryPickerSelectBtn.disabled = picker.loading || !picker.currentPath;
|
|
1820
|
+
}
|
|
1821
|
+
if (!directoryPickerList) {
|
|
1822
|
+
return;
|
|
1823
|
+
}
|
|
1824
|
+
directoryPickerList.innerHTML = '';
|
|
1825
|
+
if (picker.loading) {
|
|
1826
|
+
const loading = document.createElement('div');
|
|
1827
|
+
loading.className = 'empty';
|
|
1828
|
+
loading.textContent = '目录加载中...';
|
|
1829
|
+
directoryPickerList.appendChild(loading);
|
|
1830
|
+
return;
|
|
1831
|
+
}
|
|
1832
|
+
if (!picker.entries.length) {
|
|
1833
|
+
const empty = document.createElement('div');
|
|
1834
|
+
empty.className = 'empty';
|
|
1835
|
+
empty.textContent = '当前目录下没有可选子目录';
|
|
1836
|
+
directoryPickerList.appendChild(empty);
|
|
1837
|
+
return;
|
|
1737
1838
|
}
|
|
1839
|
+
picker.entries.forEach(function (entry) {
|
|
1840
|
+
const btn = document.createElement('button');
|
|
1841
|
+
btn.type = 'button';
|
|
1842
|
+
btn.className = 'dir-picker-item secondary';
|
|
1843
|
+
btn.textContent = entry.name;
|
|
1844
|
+
btn.addEventListener('click', function () {
|
|
1845
|
+
loadDirectoryPicker(entry.path);
|
|
1846
|
+
});
|
|
1847
|
+
directoryPickerList.appendChild(btn);
|
|
1848
|
+
});
|
|
1738
1849
|
}
|
|
1739
1850
|
|
|
1740
|
-
function
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1851
|
+
async function loadDirectoryPicker(targetPath) {
|
|
1852
|
+
const picker = state.directoryPicker;
|
|
1853
|
+
picker.loading = true;
|
|
1854
|
+
picker.error = '';
|
|
1855
|
+
if (targetPath) {
|
|
1856
|
+
picker.currentPath = targetPath;
|
|
1857
|
+
}
|
|
1858
|
+
renderDirectoryPicker();
|
|
1859
|
+
try {
|
|
1860
|
+
const params = new URLSearchParams();
|
|
1861
|
+
params.set('path', picker.currentPath || '/');
|
|
1862
|
+
if (picker.basePath) {
|
|
1863
|
+
params.set('basePath', picker.basePath);
|
|
1864
|
+
}
|
|
1865
|
+
const data = await api('/api/fs/directories?' + params.toString());
|
|
1866
|
+
picker.currentPath = data.currentPath || picker.currentPath;
|
|
1867
|
+
picker.basePath = data.basePath || picker.basePath || '';
|
|
1868
|
+
picker.parentPath = data.parentPath || '';
|
|
1869
|
+
picker.entries = Array.isArray(data.entries) ? data.entries : [];
|
|
1870
|
+
} catch (e) {
|
|
1871
|
+
picker.error = e && e.message ? e.message : '目录加载失败';
|
|
1872
|
+
picker.entries = [];
|
|
1873
|
+
} finally {
|
|
1874
|
+
picker.loading = false;
|
|
1875
|
+
renderDirectoryPicker();
|
|
1876
|
+
}
|
|
1748
1877
|
}
|
|
1749
1878
|
|
|
1750
|
-
function
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1879
|
+
function closeDirectoryPicker() {
|
|
1880
|
+
state.directoryPicker.open = false;
|
|
1881
|
+
state.directoryPicker.loading = false;
|
|
1882
|
+
state.directoryPicker.mode = '';
|
|
1883
|
+
state.directoryPicker.title = '';
|
|
1884
|
+
state.directoryPicker.tip = '';
|
|
1885
|
+
state.directoryPicker.currentPath = '';
|
|
1886
|
+
state.directoryPicker.basePath = '';
|
|
1887
|
+
state.directoryPicker.parentPath = '';
|
|
1888
|
+
state.directoryPicker.entries = [];
|
|
1889
|
+
state.directoryPicker.error = '';
|
|
1890
|
+
renderDirectoryPicker();
|
|
1754
1891
|
}
|
|
1755
1892
|
|
|
1756
|
-
function
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
row.classList.toggle('active', state.active === session.name);
|
|
1761
|
-
row.classList.toggle('history-only', status.tone === 'history');
|
|
1762
|
-
row.classList.toggle('status-running', status.tone === 'running');
|
|
1763
|
-
row.classList.toggle('status-stopped', status.tone === 'stopped');
|
|
1764
|
-
row.classList.toggle('status-history', status.tone === 'history');
|
|
1765
|
-
row.classList.toggle('status-unknown', status.tone === 'unknown');
|
|
1766
|
-
if (row.__sessionNameNode) {
|
|
1767
|
-
row.__sessionNameNode.textContent = session.name;
|
|
1768
|
-
}
|
|
1769
|
-
if (row.__statusBadgeNode) {
|
|
1770
|
-
row.__statusBadgeNode.className = `session-status ${status.tone}`;
|
|
1771
|
-
row.__statusBadgeNode.textContent = status.label;
|
|
1893
|
+
function applyPickedDirectory() {
|
|
1894
|
+
const picker = state.directoryPicker;
|
|
1895
|
+
if (!picker.currentPath) {
|
|
1896
|
+
return;
|
|
1772
1897
|
}
|
|
1773
|
-
if (
|
|
1774
|
-
|
|
1898
|
+
if (picker.mode === 'host') {
|
|
1899
|
+
createHostPath.value = picker.currentPath;
|
|
1900
|
+
if (!(createContainerPath.value || '').trim()) {
|
|
1901
|
+
createContainerPath.value = '/workspace';
|
|
1902
|
+
}
|
|
1903
|
+
} else if (picker.mode === 'container') {
|
|
1904
|
+
const mapped = buildContainerPathFromHostSelection(
|
|
1905
|
+
picker.basePath,
|
|
1906
|
+
(createContainerPath.value || '').trim() || '/workspace',
|
|
1907
|
+
picker.currentPath
|
|
1908
|
+
);
|
|
1909
|
+
createContainerPath.value = mapped;
|
|
1910
|
+
}
|
|
1911
|
+
closeDirectoryPicker();
|
|
1912
|
+
}
|
|
1913
|
+
|
|
1914
|
+
function openDirectoryPicker(mode) {
|
|
1915
|
+
const picker = state.directoryPicker;
|
|
1916
|
+
picker.open = true;
|
|
1917
|
+
picker.loading = false;
|
|
1918
|
+
picker.error = '';
|
|
1919
|
+
picker.entries = [];
|
|
1920
|
+
picker.parentPath = '';
|
|
1921
|
+
if (mode === 'container') {
|
|
1922
|
+
const baseHostPath = (createHostPath.value || '').trim();
|
|
1923
|
+
if (!baseHostPath) {
|
|
1924
|
+
showCreateError('请先选择 hostPath,再选择 containerPath。');
|
|
1925
|
+
picker.open = false;
|
|
1926
|
+
renderDirectoryPicker();
|
|
1927
|
+
return;
|
|
1928
|
+
}
|
|
1929
|
+
picker.mode = 'container';
|
|
1930
|
+
picker.title = '选择 containerPath 对应目录';
|
|
1931
|
+
picker.tip = '从 hostPath 下选择子目录,结果会映射到容器路径。';
|
|
1932
|
+
picker.basePath = baseHostPath;
|
|
1933
|
+
picker.currentPath = baseHostPath;
|
|
1934
|
+
} else {
|
|
1935
|
+
picker.mode = 'host';
|
|
1936
|
+
picker.title = '选择 hostPath';
|
|
1937
|
+
picker.tip = '浏览宿主机目录,选中后会回填 create 表单。';
|
|
1938
|
+
picker.basePath = '';
|
|
1939
|
+
picker.currentPath = (createHostPath.value || '').trim() || '/';
|
|
1775
1940
|
}
|
|
1776
|
-
|
|
1777
|
-
|
|
1941
|
+
renderDirectoryPicker();
|
|
1942
|
+
loadDirectoryPicker(picker.currentPath);
|
|
1943
|
+
}
|
|
1944
|
+
|
|
1945
|
+
function renderSessionsLoading() {
|
|
1946
|
+
state.sessionNodeMap.clear();
|
|
1947
|
+
state.sessionRenderMode = 'loading';
|
|
1948
|
+
sessionList.innerHTML = '';
|
|
1949
|
+
for (let i = 0; i < 3; i++) {
|
|
1950
|
+
const skeleton = document.createElement('div');
|
|
1951
|
+
skeleton.className = 'skeleton session';
|
|
1952
|
+
sessionList.appendChild(skeleton);
|
|
1778
1953
|
}
|
|
1779
|
-
row.__renderKey = getSessionRenderKey(session);
|
|
1780
1954
|
}
|
|
1781
1955
|
|
|
1782
1956
|
function handleSessionItemClick(sessionName) {
|
|
@@ -1807,7 +1981,7 @@
|
|
|
1807
1981
|
}
|
|
1808
1982
|
}
|
|
1809
1983
|
}
|
|
1810
|
-
|
|
1984
|
+
renderSessions();
|
|
1811
1985
|
syncUi();
|
|
1812
1986
|
Promise.all([
|
|
1813
1987
|
loadMessagesForSession(sessionName),
|
|
@@ -1817,49 +1991,113 @@
|
|
|
1817
1991
|
});
|
|
1818
1992
|
}
|
|
1819
1993
|
|
|
1820
|
-
function
|
|
1994
|
+
async function createAgentSession(containerName) {
|
|
1995
|
+
const targetContainer = String(containerName || '').trim();
|
|
1996
|
+
if (!targetContainer) {
|
|
1997
|
+
return;
|
|
1998
|
+
}
|
|
1999
|
+
try {
|
|
2000
|
+
const data = await api('/api/sessions/' + encodeURIComponent(targetContainer) + '/agents', {
|
|
2001
|
+
method: 'POST',
|
|
2002
|
+
body: JSON.stringify({})
|
|
2003
|
+
});
|
|
2004
|
+
state.activeTab = 'activity';
|
|
2005
|
+
state.mode = 'agent';
|
|
2006
|
+
await loadSessions(data.name);
|
|
2007
|
+
if (isMobileLayout()) {
|
|
2008
|
+
closeMobileSessionPanel();
|
|
2009
|
+
}
|
|
2010
|
+
} catch (e) {
|
|
2011
|
+
alert(e.message);
|
|
2012
|
+
}
|
|
2013
|
+
}
|
|
2014
|
+
|
|
2015
|
+
function groupSessionsByDirectory(sessions) {
|
|
2016
|
+
const groups = new Map();
|
|
2017
|
+
(Array.isArray(sessions) ? sessions : []).forEach(function (session) {
|
|
2018
|
+
const directoryPath = String(session && session.hostPath ? session.hostPath : '').trim() || '未配置目录';
|
|
2019
|
+
if (!groups.has(directoryPath)) {
|
|
2020
|
+
groups.set(directoryPath, {
|
|
2021
|
+
path: directoryPath,
|
|
2022
|
+
updatedAt: session && session.updatedAt ? session.updatedAt : '',
|
|
2023
|
+
containers: new Map()
|
|
2024
|
+
});
|
|
2025
|
+
}
|
|
2026
|
+
const directoryGroup = groups.get(directoryPath);
|
|
2027
|
+
if (session && session.updatedAt && (!directoryGroup.updatedAt || new Date(session.updatedAt).getTime() > new Date(directoryGroup.updatedAt).getTime())) {
|
|
2028
|
+
directoryGroup.updatedAt = session.updatedAt;
|
|
2029
|
+
}
|
|
2030
|
+
|
|
2031
|
+
const containerName = String(session && session.containerName ? session.containerName : '');
|
|
2032
|
+
if (!directoryGroup.containers.has(containerName)) {
|
|
2033
|
+
directoryGroup.containers.set(containerName, {
|
|
2034
|
+
containerName: containerName,
|
|
2035
|
+
status: session && session.status ? session.status : 'history',
|
|
2036
|
+
image: session && session.image ? session.image : '',
|
|
2037
|
+
updatedAt: session && session.updatedAt ? session.updatedAt : '',
|
|
2038
|
+
sessions: []
|
|
2039
|
+
});
|
|
2040
|
+
}
|
|
2041
|
+
const containerGroup = directoryGroup.containers.get(containerName);
|
|
2042
|
+
containerGroup.sessions.push(session);
|
|
2043
|
+
if (session && session.updatedAt && (!containerGroup.updatedAt || new Date(session.updatedAt).getTime() > new Date(containerGroup.updatedAt).getTime())) {
|
|
2044
|
+
containerGroup.updatedAt = session.updatedAt;
|
|
2045
|
+
}
|
|
2046
|
+
});
|
|
2047
|
+
return Array.from(groups.values()).sort(function (a, b) {
|
|
2048
|
+
const timeA = a.updatedAt ? new Date(a.updatedAt).getTime() : 0;
|
|
2049
|
+
const timeB = b.updatedAt ? new Date(b.updatedAt).getTime() : 0;
|
|
2050
|
+
return timeB - timeA;
|
|
2051
|
+
});
|
|
2052
|
+
}
|
|
2053
|
+
|
|
2054
|
+
function createAgentRow(session, index) {
|
|
1821
2055
|
const status = sessionStatusInfo(session.status);
|
|
1822
2056
|
const btn = document.createElement('button');
|
|
1823
2057
|
btn.type = 'button';
|
|
1824
|
-
btn.className = '
|
|
2058
|
+
btn.className = 'agent-item';
|
|
1825
2059
|
btn.dataset.sessionName = session.name;
|
|
2060
|
+
btn.style.setProperty('--item-index', String(index));
|
|
2061
|
+
btn.classList.toggle('active', state.active === session.name);
|
|
1826
2062
|
|
|
1827
2063
|
const sessionName = document.createElement('div');
|
|
1828
|
-
sessionName.className = '
|
|
2064
|
+
sessionName.className = 'agent-name';
|
|
2065
|
+
sessionName.textContent = session.agentName || session.name;
|
|
1829
2066
|
|
|
1830
2067
|
const meta = document.createElement('div');
|
|
1831
|
-
meta.className = '
|
|
2068
|
+
meta.className = 'agent-meta';
|
|
1832
2069
|
|
|
1833
2070
|
const statusBadge = document.createElement('span');
|
|
1834
2071
|
statusBadge.className = `session-status ${status.tone}`;
|
|
2072
|
+
statusBadge.textContent = status.label;
|
|
1835
2073
|
|
|
1836
2074
|
const messageCount = document.createElement('span');
|
|
1837
2075
|
messageCount.className = 'session-count';
|
|
2076
|
+
messageCount.textContent = `${safeMessageCount(session.messageCount)} 条`;
|
|
1838
2077
|
|
|
1839
2078
|
meta.appendChild(statusBadge);
|
|
1840
2079
|
meta.appendChild(messageCount);
|
|
1841
2080
|
|
|
1842
2081
|
const time = document.createElement('div');
|
|
1843
|
-
time.className = '
|
|
2082
|
+
time.className = 'agent-time';
|
|
2083
|
+
time.textContent = formatDateTime(session.updatedAt) || '暂无更新';
|
|
1844
2084
|
|
|
1845
2085
|
btn.appendChild(sessionName);
|
|
1846
2086
|
btn.appendChild(meta);
|
|
1847
2087
|
btn.appendChild(time);
|
|
1848
|
-
btn.__sessionNameNode = sessionName;
|
|
1849
|
-
btn.__statusBadgeNode = statusBadge;
|
|
1850
|
-
btn.__messageCountNode = messageCount;
|
|
1851
|
-
btn.__timeNode = time;
|
|
1852
|
-
|
|
1853
2088
|
btn.addEventListener('click', function () {
|
|
1854
2089
|
handleSessionItemClick(btn.dataset.sessionName || '');
|
|
1855
2090
|
});
|
|
1856
|
-
|
|
1857
|
-
updateSessionRow(btn, session, index);
|
|
1858
2091
|
return btn;
|
|
1859
2092
|
}
|
|
1860
2093
|
|
|
1861
2094
|
function renderSessions() {
|
|
1862
|
-
|
|
2095
|
+
const containerCount = new Set(state.sessions.map(function (session) {
|
|
2096
|
+
return session && session.containerName ? session.containerName : '';
|
|
2097
|
+
}).filter(Boolean)).size;
|
|
2098
|
+
sessionCount.textContent = state.loadingSessions
|
|
2099
|
+
? '加载中...'
|
|
2100
|
+
: `${state.sessions.length} 个 AGENT / ${containerCount} 个容器`;
|
|
1863
2101
|
|
|
1864
2102
|
if (state.loadingSessions) {
|
|
1865
2103
|
renderSessionsLoading();
|
|
@@ -1877,49 +2115,74 @@
|
|
|
1877
2115
|
return;
|
|
1878
2116
|
}
|
|
1879
2117
|
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
state.sessionRenderMode = 'list';
|
|
1884
|
-
}
|
|
1885
|
-
|
|
1886
|
-
const nextNameSet = new Set();
|
|
1887
|
-
state.sessions.forEach(function (session, index) {
|
|
1888
|
-
nextNameSet.add(session.name);
|
|
1889
|
-
let row = state.sessionNodeMap.get(session.name);
|
|
1890
|
-
if (!row) {
|
|
1891
|
-
row = createSessionRow(session, index);
|
|
1892
|
-
state.sessionNodeMap.set(session.name, row);
|
|
1893
|
-
} else if (row.__renderKey !== getSessionRenderKey(session)) {
|
|
1894
|
-
updateSessionRow(row, session, index);
|
|
1895
|
-
} else {
|
|
1896
|
-
row.style.setProperty('--item-index', String(index));
|
|
1897
|
-
}
|
|
2118
|
+
sessionList.innerHTML = '';
|
|
2119
|
+
state.sessionNodeMap.clear();
|
|
2120
|
+
state.sessionRenderMode = 'tree';
|
|
1898
2121
|
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
sessionList.insertBefore(row, currentAtIndex || null);
|
|
1902
|
-
}
|
|
1903
|
-
});
|
|
2122
|
+
const grouped = groupSessionsByDirectory(state.sessions);
|
|
2123
|
+
let itemIndex = 0;
|
|
1904
2124
|
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
removeNames.push(existingName);
|
|
1909
|
-
}
|
|
1910
|
-
}
|
|
1911
|
-
removeNames.forEach(function (name) {
|
|
1912
|
-
const row = state.sessionNodeMap.get(name);
|
|
1913
|
-
if (row && row.parentNode === sessionList) {
|
|
1914
|
-
sessionList.removeChild(row);
|
|
1915
|
-
}
|
|
1916
|
-
state.sessionNodeMap.delete(name);
|
|
1917
|
-
});
|
|
2125
|
+
grouped.forEach(function (directoryGroup) {
|
|
2126
|
+
const group = document.createElement('section');
|
|
2127
|
+
group.className = 'workbench-group';
|
|
1918
2128
|
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
2129
|
+
const groupHead = document.createElement('div');
|
|
2130
|
+
groupHead.className = 'workbench-group-head';
|
|
2131
|
+
groupHead.innerHTML = `
|
|
2132
|
+
<div class="workbench-group-kicker">目录</div>
|
|
2133
|
+
<div class="workbench-group-title">${escapeHtml(directoryGroup.path)}</div>
|
|
2134
|
+
`;
|
|
2135
|
+
group.appendChild(groupHead);
|
|
2136
|
+
|
|
2137
|
+
const containerStack = document.createElement('div');
|
|
2138
|
+
containerStack.className = 'container-stack';
|
|
2139
|
+
|
|
2140
|
+
Array.from(directoryGroup.containers.values()).forEach(function (containerGroup) {
|
|
2141
|
+
const status = sessionStatusInfo(containerGroup.status);
|
|
2142
|
+
const containerCard = document.createElement('section');
|
|
2143
|
+
containerCard.className = 'container-card';
|
|
2144
|
+
|
|
2145
|
+
const containerHead = document.createElement('div');
|
|
2146
|
+
containerHead.className = 'container-card-head';
|
|
2147
|
+
|
|
2148
|
+
const containerInfo = document.createElement('div');
|
|
2149
|
+
containerInfo.className = 'container-card-info';
|
|
2150
|
+
containerInfo.innerHTML = `
|
|
2151
|
+
<div class="container-card-kicker">容器</div>
|
|
2152
|
+
<div class="container-card-title">${escapeHtml(containerGroup.containerName)}</div>
|
|
2153
|
+
<div class="container-card-meta">
|
|
2154
|
+
<span class="session-status ${status.tone}">${escapeHtml(status.label)}</span>
|
|
2155
|
+
<span>${escapeHtml(formatDateTime(containerGroup.updatedAt) || '暂无更新')}</span>
|
|
2156
|
+
</div>
|
|
2157
|
+
`;
|
|
2158
|
+
|
|
2159
|
+
const addAgentBtn = document.createElement('button');
|
|
2160
|
+
addAgentBtn.type = 'button';
|
|
2161
|
+
addAgentBtn.className = 'secondary add-agent-btn';
|
|
2162
|
+
addAgentBtn.textContent = '新建AGENT';
|
|
2163
|
+
addAgentBtn.addEventListener('click', function () {
|
|
2164
|
+
createAgentSession(containerGroup.containerName);
|
|
2165
|
+
});
|
|
2166
|
+
|
|
2167
|
+
containerHead.appendChild(containerInfo);
|
|
2168
|
+
containerHead.appendChild(addAgentBtn);
|
|
2169
|
+
containerCard.appendChild(containerHead);
|
|
2170
|
+
|
|
2171
|
+
const agentList = document.createElement('div');
|
|
2172
|
+
agentList.className = 'agent-list';
|
|
2173
|
+
containerGroup.sessions.forEach(function (session) {
|
|
2174
|
+
const row = createAgentRow(session, itemIndex);
|
|
2175
|
+
state.sessionNodeMap.set(session.name, row);
|
|
2176
|
+
agentList.appendChild(row);
|
|
2177
|
+
itemIndex += 1;
|
|
2178
|
+
});
|
|
2179
|
+
containerCard.appendChild(agentList);
|
|
2180
|
+
containerStack.appendChild(containerCard);
|
|
2181
|
+
});
|
|
2182
|
+
|
|
2183
|
+
group.appendChild(containerStack);
|
|
2184
|
+
sessionList.appendChild(group);
|
|
2185
|
+
});
|
|
1923
2186
|
}
|
|
1924
2187
|
|
|
1925
2188
|
function renderMessagesLoading() {
|
|
@@ -2522,6 +2785,38 @@
|
|
|
2522
2785
|
});
|
|
2523
2786
|
}
|
|
2524
2787
|
|
|
2788
|
+
if (pickHostPathBtn) {
|
|
2789
|
+
pickHostPathBtn.addEventListener('click', function () {
|
|
2790
|
+
openDirectoryPicker('host');
|
|
2791
|
+
});
|
|
2792
|
+
}
|
|
2793
|
+
|
|
2794
|
+
if (pickContainerPathBtn) {
|
|
2795
|
+
pickContainerPathBtn.addEventListener('click', function () {
|
|
2796
|
+
openDirectoryPicker('container');
|
|
2797
|
+
});
|
|
2798
|
+
}
|
|
2799
|
+
|
|
2800
|
+
if (directoryPickerCancelBtn) {
|
|
2801
|
+
directoryPickerCancelBtn.addEventListener('click', function () {
|
|
2802
|
+
closeDirectoryPicker();
|
|
2803
|
+
});
|
|
2804
|
+
}
|
|
2805
|
+
|
|
2806
|
+
if (directoryPickerUpBtn) {
|
|
2807
|
+
directoryPickerUpBtn.addEventListener('click', function () {
|
|
2808
|
+
if (state.directoryPicker.parentPath) {
|
|
2809
|
+
loadDirectoryPicker(state.directoryPicker.parentPath);
|
|
2810
|
+
}
|
|
2811
|
+
});
|
|
2812
|
+
}
|
|
2813
|
+
|
|
2814
|
+
if (directoryPickerSelectBtn) {
|
|
2815
|
+
directoryPickerSelectBtn.addEventListener('click', function () {
|
|
2816
|
+
applyPickedDirectory();
|
|
2817
|
+
});
|
|
2818
|
+
}
|
|
2819
|
+
|
|
2525
2820
|
if (createRun) {
|
|
2526
2821
|
createRun.addEventListener('change', function () {
|
|
2527
2822
|
applyCurrentRunDefaults();
|
|
@@ -2822,6 +3117,14 @@
|
|
|
2822
3117
|
});
|
|
2823
3118
|
}
|
|
2824
3119
|
|
|
3120
|
+
if (directoryPickerModal) {
|
|
3121
|
+
directoryPickerModal.addEventListener('click', function (event) {
|
|
3122
|
+
if (event.target === directoryPickerModal && !state.directoryPicker.loading) {
|
|
3123
|
+
closeDirectoryPicker();
|
|
3124
|
+
}
|
|
3125
|
+
});
|
|
3126
|
+
}
|
|
3127
|
+
|
|
2825
3128
|
window.addEventListener('keydown', function (event) {
|
|
2826
3129
|
if (event.key === 'Escape' && state.configModalOpen) {
|
|
2827
3130
|
closeConfigModal();
|
|
@@ -2831,6 +3134,9 @@
|
|
|
2831
3134
|
closeCreateModal();
|
|
2832
3135
|
syncUi();
|
|
2833
3136
|
}
|
|
3137
|
+
if (event.key === 'Escape' && state.directoryPicker.open) {
|
|
3138
|
+
closeDirectoryPicker();
|
|
3139
|
+
}
|
|
2834
3140
|
if (event.key === 'Escape' && state.mobileSidebarOpen) {
|
|
2835
3141
|
closeMobileSessionPanel();
|
|
2836
3142
|
}
|
|
@@ -2877,7 +3183,9 @@
|
|
|
2877
3183
|
removeBtn.addEventListener('click', async function () {
|
|
2878
3184
|
if (!state.active) return;
|
|
2879
3185
|
closeMobileActionsMenu();
|
|
2880
|
-
const
|
|
3186
|
+
const activeSession = getActiveSession();
|
|
3187
|
+
const targetContainer = activeSession && activeSession.containerName ? activeSession.containerName : state.active;
|
|
3188
|
+
const yes = confirm('确认删除容器 ' + targetContainer + ' ?');
|
|
2881
3189
|
if (!yes) return;
|
|
2882
3190
|
try {
|
|
2883
3191
|
const current = state.active;
|
|
@@ -2896,7 +3204,9 @@
|
|
|
2896
3204
|
removeAllBtn.addEventListener('click', async function () {
|
|
2897
3205
|
if (!state.active) return;
|
|
2898
3206
|
closeMobileActionsMenu();
|
|
2899
|
-
const
|
|
3207
|
+
const activeSession = getActiveSession();
|
|
3208
|
+
const targetAgent = activeSession && activeSession.agentName ? activeSession.agentName : state.active;
|
|
3209
|
+
const yes = confirm('确认删除对话 ' + targetAgent + ' ?');
|
|
2900
3210
|
if (!yes) return;
|
|
2901
3211
|
try {
|
|
2902
3212
|
const current = state.active;
|