@xcanwin/manyoyo 5.7.3 → 5.7.6
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 +262 -0
- package/lib/web/frontend/app.html +33 -3
- package/lib/web/frontend/app.js +818 -103
- package/lib/web/server.js +512 -161
- package/package.json +1 -1
package/lib/web/frontend/app.js
CHANGED
|
@@ -63,6 +63,23 @@
|
|
|
63
63
|
createRuns: {},
|
|
64
64
|
sessionNodeMap: new Map(),
|
|
65
65
|
sessionRenderMode: 'empty',
|
|
66
|
+
sidebarTreeLoaded: false,
|
|
67
|
+
sidebarTree: {
|
|
68
|
+
directories: {},
|
|
69
|
+
containers: {}
|
|
70
|
+
},
|
|
71
|
+
pendingActiveSessionScroll: false,
|
|
72
|
+
directoryPicker: {
|
|
73
|
+
open: false,
|
|
74
|
+
loading: false,
|
|
75
|
+
mode: '',
|
|
76
|
+
title: '',
|
|
77
|
+
tip: '',
|
|
78
|
+
currentPath: '',
|
|
79
|
+
basePath: '',
|
|
80
|
+
entries: [],
|
|
81
|
+
error: ''
|
|
82
|
+
},
|
|
66
83
|
messageRequestId: 0,
|
|
67
84
|
agentRun: {
|
|
68
85
|
active: false,
|
|
@@ -99,6 +116,7 @@
|
|
|
99
116
|
const viewDetailBtn = document.getElementById('viewDetailBtn');
|
|
100
117
|
const viewConfigBtn = document.getElementById('viewConfigBtn');
|
|
101
118
|
const viewCheckBtn = document.getElementById('viewCheckBtn');
|
|
119
|
+
const addAgentBtn = document.getElementById('addAgentBtn');
|
|
102
120
|
const mobileSidebarClose = document.getElementById('mobileSidebarClose');
|
|
103
121
|
const sidebarBackdrop = document.getElementById('sidebarBackdrop');
|
|
104
122
|
const openConfigBtn = document.getElementById('openConfigBtn');
|
|
@@ -121,6 +139,8 @@
|
|
|
121
139
|
const createContainerName = document.getElementById('createContainerName');
|
|
122
140
|
const createHostPath = document.getElementById('createHostPath');
|
|
123
141
|
const createContainerPath = document.getElementById('createContainerPath');
|
|
142
|
+
const pickHostPathBtn = document.getElementById('pickHostPathBtn');
|
|
143
|
+
const pickContainerPathBtn = document.getElementById('pickContainerPathBtn');
|
|
124
144
|
const createImageName = document.getElementById('createImageName');
|
|
125
145
|
const createImageVersion = document.getElementById('createImageVersion');
|
|
126
146
|
const createContainerMode = document.getElementById('createContainerMode');
|
|
@@ -132,6 +152,15 @@
|
|
|
132
152
|
const createEnv = document.getElementById('createEnv');
|
|
133
153
|
const createEnvFile = document.getElementById('createEnvFile');
|
|
134
154
|
const createVolumes = document.getElementById('createVolumes');
|
|
155
|
+
const directoryPickerModal = document.getElementById('directoryPickerModal');
|
|
156
|
+
const directoryPickerTitle = document.getElementById('directoryPickerTitle');
|
|
157
|
+
const directoryPickerTip = document.getElementById('directoryPickerTip');
|
|
158
|
+
const directoryPickerCurrent = document.getElementById('directoryPickerCurrent');
|
|
159
|
+
const directoryPickerList = document.getElementById('directoryPickerList');
|
|
160
|
+
const directoryPickerError = document.getElementById('directoryPickerError');
|
|
161
|
+
const directoryPickerCancelBtn = document.getElementById('directoryPickerCancelBtn');
|
|
162
|
+
const directoryPickerUpBtn = document.getElementById('directoryPickerUpBtn');
|
|
163
|
+
const directoryPickerSelectBtn = document.getElementById('directoryPickerSelectBtn');
|
|
135
164
|
const activeTitle = document.getElementById('activeTitle');
|
|
136
165
|
const activeMeta = document.getElementById('activeMeta');
|
|
137
166
|
const activityCommandBtn = document.getElementById('activityCommandBtn');
|
|
@@ -183,12 +212,63 @@
|
|
|
183
212
|
const GEMINI_YOLO_FLAG = '--yolo';
|
|
184
213
|
const CODEX_DANGEROUS_FLAG = '--dangerously-bypass-approvals-and-sandbox';
|
|
185
214
|
const OPENCODE_PERMISSION_KEY = 'OPENCODE_PERMISSION=';
|
|
215
|
+
const SIDEBAR_TREE_STORAGE_KEY = 'manyoyo.web.sidebarTree.v1';
|
|
186
216
|
const markdownRenderer = window.ManyoyoMarkdown
|
|
187
217
|
&& typeof window.ManyoyoMarkdown.shouldRenderMessage === 'function'
|
|
188
218
|
&& typeof window.ManyoyoMarkdown.render === 'function'
|
|
189
219
|
? window.ManyoyoMarkdown
|
|
190
220
|
: null;
|
|
191
221
|
|
|
222
|
+
function normalizeBooleanMap(source) {
|
|
223
|
+
const result = {};
|
|
224
|
+
if (!source || typeof source !== 'object' || Array.isArray(source)) {
|
|
225
|
+
return result;
|
|
226
|
+
}
|
|
227
|
+
Object.keys(source).forEach(function (key) {
|
|
228
|
+
if (typeof source[key] === 'boolean') {
|
|
229
|
+
result[String(key)] = source[key];
|
|
230
|
+
}
|
|
231
|
+
});
|
|
232
|
+
return result;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
function loadSidebarTreeState() {
|
|
236
|
+
if (state.sidebarTreeLoaded) {
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
state.sidebarTreeLoaded = true;
|
|
240
|
+
try {
|
|
241
|
+
if (!window.localStorage) {
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
const raw = window.localStorage.getItem(SIDEBAR_TREE_STORAGE_KEY);
|
|
245
|
+
if (!raw) {
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
248
|
+
const parsed = JSON.parse(raw);
|
|
249
|
+
state.sidebarTree = {
|
|
250
|
+
directories: normalizeBooleanMap(parsed && parsed.directories),
|
|
251
|
+
containers: normalizeBooleanMap(parsed && parsed.containers)
|
|
252
|
+
};
|
|
253
|
+
} catch (e) {
|
|
254
|
+
state.sidebarTree = {
|
|
255
|
+
directories: {},
|
|
256
|
+
containers: {}
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
function persistSidebarTreeState() {
|
|
262
|
+
try {
|
|
263
|
+
if (!window.localStorage) {
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
window.localStorage.setItem(SIDEBAR_TREE_STORAGE_KEY, JSON.stringify(state.sidebarTree));
|
|
267
|
+
} catch (e) {
|
|
268
|
+
// 忽略浏览器存储异常,避免影响主流程
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
192
272
|
function appendPlainMessageContent(bubble, content) {
|
|
193
273
|
const pre = document.createElement('pre');
|
|
194
274
|
pre.textContent = content == null ? '' : String(content);
|
|
@@ -390,6 +470,9 @@
|
|
|
390
470
|
function roleName(role, message) {
|
|
391
471
|
if (role === 'user') return '我';
|
|
392
472
|
if (role === 'assistant') {
|
|
473
|
+
if (message && message.streamingReply) {
|
|
474
|
+
return 'AGENT 实时回复';
|
|
475
|
+
}
|
|
393
476
|
if (message && message.streamTrace) {
|
|
394
477
|
return 'AGENT 过程';
|
|
395
478
|
}
|
|
@@ -437,6 +520,158 @@
|
|
|
437
520
|
});
|
|
438
521
|
}
|
|
439
522
|
|
|
523
|
+
function getSessionDirectoryPath(session) {
|
|
524
|
+
return String(session && session.hostPath ? session.hostPath : '').trim() || '未配置目录';
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
function getSessionContainerName(session) {
|
|
528
|
+
return String(session && session.containerName ? session.containerName : '').trim();
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
function findSessionByName(sessionName) {
|
|
532
|
+
const target = String(sessionName || '').trim();
|
|
533
|
+
if (!target) {
|
|
534
|
+
return null;
|
|
535
|
+
}
|
|
536
|
+
return state.sessions.find(function (session) {
|
|
537
|
+
return session && session.name === target;
|
|
538
|
+
}) || null;
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
function pruneSidebarTreeState() {
|
|
542
|
+
loadSidebarTreeState();
|
|
543
|
+
|
|
544
|
+
const validDirectories = new Set(state.sessions.map(getSessionDirectoryPath));
|
|
545
|
+
const validContainers = new Set(state.sessions.map(getSessionContainerName).filter(Boolean));
|
|
546
|
+
let changed = false;
|
|
547
|
+
|
|
548
|
+
Object.keys(state.sidebarTree.directories).forEach(function (key) {
|
|
549
|
+
if (!validDirectories.has(key)) {
|
|
550
|
+
delete state.sidebarTree.directories[key];
|
|
551
|
+
changed = true;
|
|
552
|
+
}
|
|
553
|
+
});
|
|
554
|
+
|
|
555
|
+
Object.keys(state.sidebarTree.containers).forEach(function (key) {
|
|
556
|
+
if (!validContainers.has(key)) {
|
|
557
|
+
delete state.sidebarTree.containers[key];
|
|
558
|
+
changed = true;
|
|
559
|
+
}
|
|
560
|
+
});
|
|
561
|
+
|
|
562
|
+
if (changed) {
|
|
563
|
+
persistSidebarTreeState();
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
function setSidebarDirectoryExpanded(directoryPath, expanded, options) {
|
|
568
|
+
const path = String(directoryPath || '').trim();
|
|
569
|
+
if (!path) {
|
|
570
|
+
return false;
|
|
571
|
+
}
|
|
572
|
+
loadSidebarTreeState();
|
|
573
|
+
const opts = options && typeof options === 'object' ? options : {};
|
|
574
|
+
if (state.sidebarTree.directories[path] === expanded) {
|
|
575
|
+
return false;
|
|
576
|
+
}
|
|
577
|
+
state.sidebarTree.directories[path] = expanded;
|
|
578
|
+
if (opts.persist !== false) {
|
|
579
|
+
persistSidebarTreeState();
|
|
580
|
+
}
|
|
581
|
+
return true;
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
function setSidebarContainerExpanded(containerName, expanded, options) {
|
|
585
|
+
const name = String(containerName || '').trim();
|
|
586
|
+
if (!name) {
|
|
587
|
+
return false;
|
|
588
|
+
}
|
|
589
|
+
loadSidebarTreeState();
|
|
590
|
+
const opts = options && typeof options === 'object' ? options : {};
|
|
591
|
+
if (state.sidebarTree.containers[name] === expanded) {
|
|
592
|
+
return false;
|
|
593
|
+
}
|
|
594
|
+
state.sidebarTree.containers[name] = expanded;
|
|
595
|
+
if (opts.persist !== false) {
|
|
596
|
+
persistSidebarTreeState();
|
|
597
|
+
}
|
|
598
|
+
return true;
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
function ensureSessionPathExpanded(sessionName, options) {
|
|
602
|
+
const session = findSessionByName(sessionName);
|
|
603
|
+
if (!session) {
|
|
604
|
+
return false;
|
|
605
|
+
}
|
|
606
|
+
const opts = options && typeof options === 'object' ? options : {};
|
|
607
|
+
const directoryPath = getSessionDirectoryPath(session);
|
|
608
|
+
const containerName = getSessionContainerName(session);
|
|
609
|
+
const changedDirectory = setSidebarDirectoryExpanded(directoryPath, true, { persist: false });
|
|
610
|
+
const changedContainer = setSidebarContainerExpanded(containerName, true, { persist: false });
|
|
611
|
+
if ((changedDirectory || changedContainer) && opts.persist !== false) {
|
|
612
|
+
persistSidebarTreeState();
|
|
613
|
+
}
|
|
614
|
+
return changedDirectory || changedContainer;
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
function directoryContainsActiveSession(directoryGroup) {
|
|
618
|
+
const groups = directoryGroup && Array.isArray(directoryGroup.containers) ? directoryGroup.containers : [];
|
|
619
|
+
return groups.some(function (containerGroup) {
|
|
620
|
+
return containerContainsActiveSession(containerGroup);
|
|
621
|
+
});
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
function containerContainsActiveSession(containerGroup) {
|
|
625
|
+
const sessions = containerGroup && Array.isArray(containerGroup.sessions) ? containerGroup.sessions : [];
|
|
626
|
+
return sessions.some(function (session) {
|
|
627
|
+
return session && session.name === state.active;
|
|
628
|
+
});
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
function isDirectoryExpanded(directoryGroup) {
|
|
632
|
+
loadSidebarTreeState();
|
|
633
|
+
const key = String(directoryGroup && directoryGroup.path ? directoryGroup.path : '').trim();
|
|
634
|
+
if (key && typeof state.sidebarTree.directories[key] === 'boolean') {
|
|
635
|
+
return state.sidebarTree.directories[key];
|
|
636
|
+
}
|
|
637
|
+
return false;
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
function isContainerExpanded(containerGroup) {
|
|
641
|
+
loadSidebarTreeState();
|
|
642
|
+
const key = String(containerGroup && containerGroup.containerName ? containerGroup.containerName : '').trim();
|
|
643
|
+
if (key && typeof state.sidebarTree.containers[key] === 'boolean') {
|
|
644
|
+
return state.sidebarTree.containers[key];
|
|
645
|
+
}
|
|
646
|
+
return false;
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
function normalizeSlashPath(value) {
|
|
650
|
+
return String(value || '').replace(/\\/g, '/');
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
function isChildPath(basePath, targetPath) {
|
|
654
|
+
const normalizedBase = normalizeSlashPath(basePath).replace(/\/+$/, '');
|
|
655
|
+
const normalizedTarget = normalizeSlashPath(targetPath).replace(/\/+$/, '');
|
|
656
|
+
if (!normalizedBase) {
|
|
657
|
+
return false;
|
|
658
|
+
}
|
|
659
|
+
return normalizedTarget === normalizedBase || normalizedTarget.startsWith(normalizedBase + '/');
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
function buildContainerPathFromHostSelection(baseHostPath, baseContainerPath, selectedHostPath) {
|
|
663
|
+
const normalizedBaseHost = normalizeSlashPath(baseHostPath).replace(/\/+$/, '');
|
|
664
|
+
const normalizedContainer = normalizeSlashPath(baseContainerPath).replace(/\/+$/, '') || '/workspace';
|
|
665
|
+
const normalizedSelected = normalizeSlashPath(selectedHostPath).replace(/\/+$/, '');
|
|
666
|
+
if (!normalizedBaseHost || !isChildPath(normalizedBaseHost, normalizedSelected)) {
|
|
667
|
+
return normalizedContainer;
|
|
668
|
+
}
|
|
669
|
+
const relative = normalizedSelected === normalizedBaseHost
|
|
670
|
+
? ''
|
|
671
|
+
: normalizedSelected.slice(normalizedBaseHost.length + 1);
|
|
672
|
+
return relative ? `${normalizedContainer}/${relative}`.replace(/\/+/g, '/') : normalizedContainer;
|
|
673
|
+
}
|
|
674
|
+
|
|
440
675
|
function setModalVisible(modalNode, visible) {
|
|
441
676
|
if (!modalNode) return;
|
|
442
677
|
modalNode.hidden = !visible;
|
|
@@ -467,6 +702,18 @@
|
|
|
467
702
|
configError.textContent = text;
|
|
468
703
|
}
|
|
469
704
|
|
|
705
|
+
function showDirectoryPickerError(message) {
|
|
706
|
+
if (!directoryPickerError) return;
|
|
707
|
+
const text = String(message || '').trim();
|
|
708
|
+
if (!text) {
|
|
709
|
+
directoryPickerError.hidden = true;
|
|
710
|
+
directoryPickerError.textContent = '';
|
|
711
|
+
return;
|
|
712
|
+
}
|
|
713
|
+
directoryPickerError.hidden = false;
|
|
714
|
+
directoryPickerError.textContent = text;
|
|
715
|
+
}
|
|
716
|
+
|
|
470
717
|
function envMapToText(envMap) {
|
|
471
718
|
if (!envMap || typeof envMap !== 'object') {
|
|
472
719
|
return '';
|
|
@@ -794,7 +1041,8 @@
|
|
|
794
1041
|
const status = sessionStatusInfo(session.status);
|
|
795
1042
|
const messageCount = safeMessageCount(session.messageCount);
|
|
796
1043
|
const updatedAt = formatDateTime(session.updatedAt) || '暂无更新';
|
|
797
|
-
|
|
1044
|
+
const containerName = session.containerName || '未绑定容器';
|
|
1045
|
+
return `${containerName} · ${status.label} · ${messageCount} 条对话 · ${updatedAt}`;
|
|
798
1046
|
}
|
|
799
1047
|
|
|
800
1048
|
function buildMessageMetaLines(message) {
|
|
@@ -1309,7 +1557,8 @@
|
|
|
1309
1557
|
if (detailSummary) {
|
|
1310
1558
|
detailSummary.innerHTML = '';
|
|
1311
1559
|
renderKeyValueCard(detailSummary, '会话概览', [
|
|
1312
|
-
{ label: '
|
|
1560
|
+
{ label: 'AGENT', value: detail.agentName || detail.name || state.active },
|
|
1561
|
+
{ label: '容器', value: detail.containerName || '—' },
|
|
1313
1562
|
{ label: '状态', value: status.label, tone: status.tone },
|
|
1314
1563
|
{ label: '镜像', value: detail.image || applied.imageName || '—' },
|
|
1315
1564
|
{ label: '最近更新', value: updatedText },
|
|
@@ -1332,7 +1581,8 @@
|
|
|
1332
1581
|
if (configSummary) {
|
|
1333
1582
|
configSummary.innerHTML = '';
|
|
1334
1583
|
renderKeyValueCard(configSummary, '基础配置', [
|
|
1335
|
-
{ label: '
|
|
1584
|
+
{ label: 'AGENT', value: detail.agentName || '—' },
|
|
1585
|
+
{ label: 'containerName', value: applied.containerName || detail.containerName || '—' },
|
|
1336
1586
|
{ label: 'imageName', value: applied.imageName || detail.image || '—' },
|
|
1337
1587
|
{ label: 'imageVersion', value: applied.imageVersion || '—' },
|
|
1338
1588
|
{ label: 'containerMode', value: applied.containerMode || 'default' }
|
|
@@ -1406,8 +1656,9 @@
|
|
|
1406
1656
|
commandInput.value = '';
|
|
1407
1657
|
}
|
|
1408
1658
|
} else {
|
|
1409
|
-
|
|
1410
|
-
|
|
1659
|
+
const activeSession = getActiveSession();
|
|
1660
|
+
activeTitle.textContent = activeSession && activeSession.agentName ? activeSession.agentName : state.active;
|
|
1661
|
+
activeMeta.textContent = buildActiveMeta(activeSession);
|
|
1411
1662
|
}
|
|
1412
1663
|
|
|
1413
1664
|
const activityTab = state.activeTab === 'activity';
|
|
@@ -1460,6 +1711,9 @@
|
|
|
1460
1711
|
const activeAgentRunning = isAgentRunActiveForSession(state.active);
|
|
1461
1712
|
const busy = state.loadingSessions || state.loadingMessages || state.sending;
|
|
1462
1713
|
refreshBtn.disabled = busy;
|
|
1714
|
+
if (addAgentBtn) {
|
|
1715
|
+
addAgentBtn.disabled = !state.active || busy;
|
|
1716
|
+
}
|
|
1463
1717
|
removeBtn.disabled = !state.active || busy;
|
|
1464
1718
|
removeAllBtn.disabled = !state.active || busy;
|
|
1465
1719
|
sendBtn.disabled = !activityTab || !state.active || busy || (agentMode && !agentEnabled);
|
|
@@ -1522,6 +1776,13 @@
|
|
|
1522
1776
|
if (createModal) {
|
|
1523
1777
|
createModal.hidden = !state.createModalOpen;
|
|
1524
1778
|
}
|
|
1779
|
+
if (directoryPickerModal) {
|
|
1780
|
+
directoryPickerModal.hidden = !state.directoryPicker.open;
|
|
1781
|
+
}
|
|
1782
|
+
document.body.classList.toggle(
|
|
1783
|
+
'modal-open',
|
|
1784
|
+
state.configModalOpen || state.createModalOpen || state.directoryPicker.open
|
|
1785
|
+
);
|
|
1525
1786
|
if (!state.active) {
|
|
1526
1787
|
sendState.textContent = '未选择会话';
|
|
1527
1788
|
} else if (agentMode && !agentEnabled) {
|
|
@@ -1718,6 +1979,7 @@
|
|
|
1718
1979
|
function closeCreateModal() {
|
|
1719
1980
|
state.createModalOpen = false;
|
|
1720
1981
|
setModalVisible(createModal, false);
|
|
1982
|
+
closeDirectoryPicker();
|
|
1721
1983
|
showCreateError('');
|
|
1722
1984
|
}
|
|
1723
1985
|
|
|
@@ -1726,57 +1988,159 @@
|
|
|
1726
1988
|
showCreateError('');
|
|
1727
1989
|
}
|
|
1728
1990
|
|
|
1729
|
-
function
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1991
|
+
function renderDirectoryPicker() {
|
|
1992
|
+
if (!directoryPickerModal) return;
|
|
1993
|
+
const picker = state.directoryPicker;
|
|
1994
|
+
setModalVisible(directoryPickerModal, picker.open);
|
|
1995
|
+
if (directoryPickerTitle) {
|
|
1996
|
+
directoryPickerTitle.textContent = picker.title || '选择目录';
|
|
1997
|
+
}
|
|
1998
|
+
if (directoryPickerTip) {
|
|
1999
|
+
directoryPickerTip.textContent = picker.tip || '';
|
|
2000
|
+
}
|
|
2001
|
+
if (directoryPickerCurrent) {
|
|
2002
|
+
directoryPickerCurrent.textContent = picker.currentPath || '未选择目录';
|
|
2003
|
+
}
|
|
2004
|
+
showDirectoryPickerError(picker.error);
|
|
2005
|
+
if (directoryPickerUpBtn) {
|
|
2006
|
+
directoryPickerUpBtn.disabled = picker.loading || !picker.currentPath || !picker.parentPath;
|
|
2007
|
+
}
|
|
2008
|
+
if (directoryPickerSelectBtn) {
|
|
2009
|
+
directoryPickerSelectBtn.disabled = picker.loading || !picker.currentPath;
|
|
2010
|
+
}
|
|
2011
|
+
if (!directoryPickerList) {
|
|
2012
|
+
return;
|
|
2013
|
+
}
|
|
2014
|
+
directoryPickerList.innerHTML = '';
|
|
2015
|
+
if (picker.loading) {
|
|
2016
|
+
const loading = document.createElement('div');
|
|
2017
|
+
loading.className = 'empty';
|
|
2018
|
+
loading.textContent = '目录加载中...';
|
|
2019
|
+
directoryPickerList.appendChild(loading);
|
|
2020
|
+
return;
|
|
1737
2021
|
}
|
|
2022
|
+
if (!picker.entries.length) {
|
|
2023
|
+
const empty = document.createElement('div');
|
|
2024
|
+
empty.className = 'empty';
|
|
2025
|
+
empty.textContent = '当前目录下没有可选子目录';
|
|
2026
|
+
directoryPickerList.appendChild(empty);
|
|
2027
|
+
return;
|
|
2028
|
+
}
|
|
2029
|
+
picker.entries.forEach(function (entry) {
|
|
2030
|
+
const btn = document.createElement('button');
|
|
2031
|
+
btn.type = 'button';
|
|
2032
|
+
btn.className = 'dir-picker-item secondary';
|
|
2033
|
+
btn.textContent = entry.name;
|
|
2034
|
+
btn.addEventListener('click', function () {
|
|
2035
|
+
loadDirectoryPicker(entry.path);
|
|
2036
|
+
});
|
|
2037
|
+
directoryPickerList.appendChild(btn);
|
|
2038
|
+
});
|
|
1738
2039
|
}
|
|
1739
2040
|
|
|
1740
|
-
function
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
2041
|
+
async function loadDirectoryPicker(targetPath) {
|
|
2042
|
+
const picker = state.directoryPicker;
|
|
2043
|
+
picker.loading = true;
|
|
2044
|
+
picker.error = '';
|
|
2045
|
+
if (targetPath) {
|
|
2046
|
+
picker.currentPath = targetPath;
|
|
2047
|
+
}
|
|
2048
|
+
renderDirectoryPicker();
|
|
2049
|
+
try {
|
|
2050
|
+
const params = new URLSearchParams();
|
|
2051
|
+
params.set('path', picker.currentPath || '/');
|
|
2052
|
+
if (picker.basePath) {
|
|
2053
|
+
params.set('basePath', picker.basePath);
|
|
2054
|
+
}
|
|
2055
|
+
const data = await api('/api/fs/directories?' + params.toString());
|
|
2056
|
+
picker.currentPath = data.currentPath || picker.currentPath;
|
|
2057
|
+
picker.basePath = data.basePath || picker.basePath || '';
|
|
2058
|
+
picker.parentPath = data.parentPath || '';
|
|
2059
|
+
picker.entries = Array.isArray(data.entries) ? data.entries : [];
|
|
2060
|
+
} catch (e) {
|
|
2061
|
+
picker.error = e && e.message ? e.message : '目录加载失败';
|
|
2062
|
+
picker.entries = [];
|
|
2063
|
+
} finally {
|
|
2064
|
+
picker.loading = false;
|
|
2065
|
+
renderDirectoryPicker();
|
|
2066
|
+
}
|
|
1748
2067
|
}
|
|
1749
2068
|
|
|
1750
|
-
function
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
2069
|
+
function closeDirectoryPicker() {
|
|
2070
|
+
state.directoryPicker.open = false;
|
|
2071
|
+
state.directoryPicker.loading = false;
|
|
2072
|
+
state.directoryPicker.mode = '';
|
|
2073
|
+
state.directoryPicker.title = '';
|
|
2074
|
+
state.directoryPicker.tip = '';
|
|
2075
|
+
state.directoryPicker.currentPath = '';
|
|
2076
|
+
state.directoryPicker.basePath = '';
|
|
2077
|
+
state.directoryPicker.parentPath = '';
|
|
2078
|
+
state.directoryPicker.entries = [];
|
|
2079
|
+
state.directoryPicker.error = '';
|
|
2080
|
+
renderDirectoryPicker();
|
|
1754
2081
|
}
|
|
1755
2082
|
|
|
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;
|
|
2083
|
+
function applyPickedDirectory() {
|
|
2084
|
+
const picker = state.directoryPicker;
|
|
2085
|
+
if (!picker.currentPath) {
|
|
2086
|
+
return;
|
|
1772
2087
|
}
|
|
1773
|
-
if (
|
|
1774
|
-
|
|
2088
|
+
if (picker.mode === 'host') {
|
|
2089
|
+
createHostPath.value = picker.currentPath;
|
|
2090
|
+
if (!(createContainerPath.value || '').trim()) {
|
|
2091
|
+
createContainerPath.value = '/workspace';
|
|
2092
|
+
}
|
|
2093
|
+
} else if (picker.mode === 'container') {
|
|
2094
|
+
const mapped = buildContainerPathFromHostSelection(
|
|
2095
|
+
picker.basePath,
|
|
2096
|
+
(createContainerPath.value || '').trim() || '/workspace',
|
|
2097
|
+
picker.currentPath
|
|
2098
|
+
);
|
|
2099
|
+
createContainerPath.value = mapped;
|
|
2100
|
+
}
|
|
2101
|
+
closeDirectoryPicker();
|
|
2102
|
+
}
|
|
2103
|
+
|
|
2104
|
+
function openDirectoryPicker(mode) {
|
|
2105
|
+
const picker = state.directoryPicker;
|
|
2106
|
+
picker.open = true;
|
|
2107
|
+
picker.loading = false;
|
|
2108
|
+
picker.error = '';
|
|
2109
|
+
picker.entries = [];
|
|
2110
|
+
picker.parentPath = '';
|
|
2111
|
+
if (mode === 'container') {
|
|
2112
|
+
const baseHostPath = (createHostPath.value || '').trim();
|
|
2113
|
+
if (!baseHostPath) {
|
|
2114
|
+
showCreateError('请先选择 hostPath,再选择 containerPath。');
|
|
2115
|
+
picker.open = false;
|
|
2116
|
+
renderDirectoryPicker();
|
|
2117
|
+
return;
|
|
2118
|
+
}
|
|
2119
|
+
picker.mode = 'container';
|
|
2120
|
+
picker.title = '选择 containerPath 对应目录';
|
|
2121
|
+
picker.tip = '从 hostPath 下选择子目录,结果会映射到容器路径。';
|
|
2122
|
+
picker.basePath = baseHostPath;
|
|
2123
|
+
picker.currentPath = baseHostPath;
|
|
2124
|
+
} else {
|
|
2125
|
+
picker.mode = 'host';
|
|
2126
|
+
picker.title = '选择 hostPath';
|
|
2127
|
+
picker.tip = '浏览宿主机目录,选中后会回填 create 表单。';
|
|
2128
|
+
picker.basePath = '';
|
|
2129
|
+
picker.currentPath = (createHostPath.value || '').trim() || '/';
|
|
1775
2130
|
}
|
|
1776
|
-
|
|
1777
|
-
|
|
2131
|
+
renderDirectoryPicker();
|
|
2132
|
+
loadDirectoryPicker(picker.currentPath);
|
|
2133
|
+
}
|
|
2134
|
+
|
|
2135
|
+
function renderSessionsLoading() {
|
|
2136
|
+
state.sessionNodeMap.clear();
|
|
2137
|
+
state.sessionRenderMode = 'loading';
|
|
2138
|
+
sessionList.innerHTML = '';
|
|
2139
|
+
for (let i = 0; i < 3; i++) {
|
|
2140
|
+
const skeleton = document.createElement('div');
|
|
2141
|
+
skeleton.className = 'skeleton session';
|
|
2142
|
+
sessionList.appendChild(skeleton);
|
|
1778
2143
|
}
|
|
1779
|
-
row.__renderKey = getSessionRenderKey(session);
|
|
1780
2144
|
}
|
|
1781
2145
|
|
|
1782
2146
|
function handleSessionItemClick(sessionName) {
|
|
@@ -1792,6 +2156,7 @@
|
|
|
1792
2156
|
disconnectTerminal('会话切换,终端已断开', true);
|
|
1793
2157
|
}
|
|
1794
2158
|
state.active = sessionName;
|
|
2159
|
+
ensureSessionPathExpanded(sessionName);
|
|
1795
2160
|
state.sessionDetail = null;
|
|
1796
2161
|
state.sessionDetailError = '';
|
|
1797
2162
|
if (isMobileLayout()) {
|
|
@@ -1807,7 +2172,7 @@
|
|
|
1807
2172
|
}
|
|
1808
2173
|
}
|
|
1809
2174
|
}
|
|
1810
|
-
|
|
2175
|
+
renderSessions();
|
|
1811
2176
|
syncUi();
|
|
1812
2177
|
Promise.all([
|
|
1813
2178
|
loadMessagesForSession(sessionName),
|
|
@@ -1817,49 +2182,187 @@
|
|
|
1817
2182
|
});
|
|
1818
2183
|
}
|
|
1819
2184
|
|
|
1820
|
-
function
|
|
2185
|
+
async function createAgentSession(containerName) {
|
|
2186
|
+
const targetContainer = String(containerName || '').trim();
|
|
2187
|
+
if (!targetContainer) {
|
|
2188
|
+
return;
|
|
2189
|
+
}
|
|
2190
|
+
try {
|
|
2191
|
+
const data = await api('/api/sessions/' + encodeURIComponent(targetContainer) + '/agents', {
|
|
2192
|
+
method: 'POST',
|
|
2193
|
+
body: JSON.stringify({})
|
|
2194
|
+
});
|
|
2195
|
+
state.activeTab = 'activity';
|
|
2196
|
+
state.mode = 'agent';
|
|
2197
|
+
await loadSessions(data.name);
|
|
2198
|
+
if (isMobileLayout()) {
|
|
2199
|
+
closeMobileSessionPanel();
|
|
2200
|
+
}
|
|
2201
|
+
} catch (e) {
|
|
2202
|
+
alert(e.message);
|
|
2203
|
+
}
|
|
2204
|
+
}
|
|
2205
|
+
|
|
2206
|
+
function groupSessionsByDirectory(sessions) {
|
|
2207
|
+
const groups = new Map();
|
|
2208
|
+
(Array.isArray(sessions) ? sessions : []).forEach(function (session) {
|
|
2209
|
+
const directoryPath = String(session && session.hostPath ? session.hostPath : '').trim() || '未配置目录';
|
|
2210
|
+
if (!groups.has(directoryPath)) {
|
|
2211
|
+
groups.set(directoryPath, {
|
|
2212
|
+
path: directoryPath,
|
|
2213
|
+
updatedAt: session && session.updatedAt ? session.updatedAt : '',
|
|
2214
|
+
containers: new Map()
|
|
2215
|
+
});
|
|
2216
|
+
}
|
|
2217
|
+
const directoryGroup = groups.get(directoryPath);
|
|
2218
|
+
if (session && session.updatedAt && (!directoryGroup.updatedAt || new Date(session.updatedAt).getTime() > new Date(directoryGroup.updatedAt).getTime())) {
|
|
2219
|
+
directoryGroup.updatedAt = session.updatedAt;
|
|
2220
|
+
}
|
|
2221
|
+
|
|
2222
|
+
const containerName = String(session && session.containerName ? session.containerName : '');
|
|
2223
|
+
if (!directoryGroup.containers.has(containerName)) {
|
|
2224
|
+
directoryGroup.containers.set(containerName, {
|
|
2225
|
+
containerName: containerName,
|
|
2226
|
+
status: session && session.status ? session.status : 'history',
|
|
2227
|
+
image: session && session.image ? session.image : '',
|
|
2228
|
+
updatedAt: session && session.updatedAt ? session.updatedAt : '',
|
|
2229
|
+
sessions: []
|
|
2230
|
+
});
|
|
2231
|
+
}
|
|
2232
|
+
const containerGroup = directoryGroup.containers.get(containerName);
|
|
2233
|
+
containerGroup.sessions.push(session);
|
|
2234
|
+
if (session && session.updatedAt && (!containerGroup.updatedAt || new Date(session.updatedAt).getTime() > new Date(containerGroup.updatedAt).getTime())) {
|
|
2235
|
+
containerGroup.updatedAt = session.updatedAt;
|
|
2236
|
+
}
|
|
2237
|
+
});
|
|
2238
|
+
return Array.from(groups.values()).map(function (group) {
|
|
2239
|
+
group.containers = Array.from(group.containers.values()).sort(function (a, b) {
|
|
2240
|
+
const timeA = a.updatedAt ? new Date(a.updatedAt).getTime() : 0;
|
|
2241
|
+
const timeB = b.updatedAt ? new Date(b.updatedAt).getTime() : 0;
|
|
2242
|
+
return timeB - timeA;
|
|
2243
|
+
});
|
|
2244
|
+
group.containers.forEach(function (containerGroup) {
|
|
2245
|
+
containerGroup.sessions.sort(function (a, b) {
|
|
2246
|
+
const timeA = a && a.updatedAt ? new Date(a.updatedAt).getTime() : 0;
|
|
2247
|
+
const timeB = b && b.updatedAt ? new Date(b.updatedAt).getTime() : 0;
|
|
2248
|
+
return timeB - timeA;
|
|
2249
|
+
});
|
|
2250
|
+
});
|
|
2251
|
+
return group;
|
|
2252
|
+
}).sort(function (a, b) {
|
|
2253
|
+
const timeA = a.updatedAt ? new Date(a.updatedAt).getTime() : 0;
|
|
2254
|
+
const timeB = b.updatedAt ? new Date(b.updatedAt).getTime() : 0;
|
|
2255
|
+
return timeB - timeA;
|
|
2256
|
+
});
|
|
2257
|
+
}
|
|
2258
|
+
|
|
2259
|
+
function createTreeMetaText(parts) {
|
|
2260
|
+
return (parts || []).filter(Boolean).join(' · ');
|
|
2261
|
+
}
|
|
2262
|
+
|
|
2263
|
+
function createTreeToggle(options) {
|
|
2264
|
+
const opts = options && typeof options === 'object' ? options : {};
|
|
2265
|
+
const button = document.createElement('button');
|
|
2266
|
+
button.type = 'button';
|
|
2267
|
+
button.className = `tree-toggle ${opts.className || ''}`.trim();
|
|
2268
|
+
button.setAttribute('aria-expanded', opts.expanded ? 'true' : 'false');
|
|
2269
|
+
|
|
2270
|
+
const main = document.createElement('div');
|
|
2271
|
+
main.className = 'tree-toggle-main';
|
|
2272
|
+
|
|
2273
|
+
const kicker = document.createElement('div');
|
|
2274
|
+
kicker.className = opts.kickerClassName || 'workbench-group-kicker';
|
|
2275
|
+
kicker.textContent = opts.kicker || '';
|
|
2276
|
+
main.appendChild(kicker);
|
|
2277
|
+
|
|
2278
|
+
const title = document.createElement('div');
|
|
2279
|
+
title.className = opts.titleClassName || 'workbench-group-title';
|
|
2280
|
+
title.textContent = opts.title || '';
|
|
2281
|
+
main.appendChild(title);
|
|
2282
|
+
|
|
2283
|
+
const metaText = createTreeMetaText(opts.metaParts);
|
|
2284
|
+
if (metaText) {
|
|
2285
|
+
const meta = document.createElement('div');
|
|
2286
|
+
meta.className = 'tree-toggle-meta';
|
|
2287
|
+
meta.textContent = metaText;
|
|
2288
|
+
main.appendChild(meta);
|
|
2289
|
+
}
|
|
2290
|
+
|
|
2291
|
+
const caret = document.createElement('span');
|
|
2292
|
+
caret.className = 'tree-toggle-caret';
|
|
2293
|
+
caret.setAttribute('aria-hidden', 'true');
|
|
2294
|
+
caret.textContent = '›';
|
|
2295
|
+
|
|
2296
|
+
button.appendChild(main);
|
|
2297
|
+
button.appendChild(caret);
|
|
2298
|
+
|
|
2299
|
+
if (typeof opts.onClick === 'function') {
|
|
2300
|
+
button.addEventListener('click', opts.onClick);
|
|
2301
|
+
}
|
|
2302
|
+
|
|
2303
|
+
return button;
|
|
2304
|
+
}
|
|
2305
|
+
|
|
2306
|
+
function createAgentRow(session, index) {
|
|
1821
2307
|
const status = sessionStatusInfo(session.status);
|
|
1822
2308
|
const btn = document.createElement('button');
|
|
1823
2309
|
btn.type = 'button';
|
|
1824
|
-
btn.className = '
|
|
2310
|
+
btn.className = 'agent-item';
|
|
1825
2311
|
btn.dataset.sessionName = session.name;
|
|
2312
|
+
btn.style.setProperty('--item-index', String(index));
|
|
2313
|
+
btn.classList.toggle('active', state.active === session.name);
|
|
1826
2314
|
|
|
1827
2315
|
const sessionName = document.createElement('div');
|
|
1828
|
-
sessionName.className = '
|
|
2316
|
+
sessionName.className = 'agent-name';
|
|
2317
|
+
sessionName.textContent = session.agentName || session.name;
|
|
1829
2318
|
|
|
1830
2319
|
const meta = document.createElement('div');
|
|
1831
|
-
meta.className = '
|
|
2320
|
+
meta.className = 'agent-meta';
|
|
1832
2321
|
|
|
1833
2322
|
const statusBadge = document.createElement('span');
|
|
1834
2323
|
statusBadge.className = `session-status ${status.tone}`;
|
|
2324
|
+
statusBadge.textContent = status.label;
|
|
1835
2325
|
|
|
1836
2326
|
const messageCount = document.createElement('span');
|
|
1837
2327
|
messageCount.className = 'session-count';
|
|
2328
|
+
messageCount.textContent = `${safeMessageCount(session.messageCount)} 条`;
|
|
1838
2329
|
|
|
1839
2330
|
meta.appendChild(statusBadge);
|
|
1840
2331
|
meta.appendChild(messageCount);
|
|
1841
2332
|
|
|
1842
2333
|
const time = document.createElement('div');
|
|
1843
|
-
time.className = '
|
|
2334
|
+
time.className = 'agent-time';
|
|
2335
|
+
time.textContent = formatDateTime(session.updatedAt) || '暂无更新';
|
|
1844
2336
|
|
|
1845
2337
|
btn.appendChild(sessionName);
|
|
1846
2338
|
btn.appendChild(meta);
|
|
1847
2339
|
btn.appendChild(time);
|
|
1848
|
-
btn.__sessionNameNode = sessionName;
|
|
1849
|
-
btn.__statusBadgeNode = statusBadge;
|
|
1850
|
-
btn.__messageCountNode = messageCount;
|
|
1851
|
-
btn.__timeNode = time;
|
|
1852
|
-
|
|
1853
2340
|
btn.addEventListener('click', function () {
|
|
1854
2341
|
handleSessionItemClick(btn.dataset.sessionName || '');
|
|
1855
2342
|
});
|
|
1856
|
-
|
|
1857
|
-
updateSessionRow(btn, session, index);
|
|
1858
2343
|
return btn;
|
|
1859
2344
|
}
|
|
1860
2345
|
|
|
2346
|
+
function scrollActiveSessionIntoView() {
|
|
2347
|
+
if (!state.pendingActiveSessionScroll || !state.active) {
|
|
2348
|
+
return;
|
|
2349
|
+
}
|
|
2350
|
+
state.pendingActiveSessionScroll = false;
|
|
2351
|
+
const targetNode = state.sessionNodeMap.get(state.active);
|
|
2352
|
+
if (targetNode && typeof targetNode.scrollIntoView === 'function') {
|
|
2353
|
+
targetNode.scrollIntoView({
|
|
2354
|
+
block: 'nearest'
|
|
2355
|
+
});
|
|
2356
|
+
}
|
|
2357
|
+
}
|
|
2358
|
+
|
|
1861
2359
|
function renderSessions() {
|
|
1862
|
-
|
|
2360
|
+
const containerCount = new Set(state.sessions.map(function (session) {
|
|
2361
|
+
return session && session.containerName ? session.containerName : '';
|
|
2362
|
+
}).filter(Boolean)).size;
|
|
2363
|
+
sessionCount.textContent = state.loadingSessions
|
|
2364
|
+
? '加载中...'
|
|
2365
|
+
: `${state.sessions.length} 个 AGENT / ${containerCount} 个容器`;
|
|
1863
2366
|
|
|
1864
2367
|
if (state.loadingSessions) {
|
|
1865
2368
|
renderSessionsLoading();
|
|
@@ -1877,49 +2380,104 @@
|
|
|
1877
2380
|
return;
|
|
1878
2381
|
}
|
|
1879
2382
|
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
2383
|
+
sessionList.innerHTML = '';
|
|
2384
|
+
state.sessionNodeMap.clear();
|
|
2385
|
+
state.sessionRenderMode = 'tree';
|
|
2386
|
+
|
|
2387
|
+
const grouped = groupSessionsByDirectory(state.sessions);
|
|
2388
|
+
let itemIndex = 0;
|
|
2389
|
+
|
|
2390
|
+
grouped.forEach(function (directoryGroup) {
|
|
2391
|
+
const directoryExpanded = isDirectoryExpanded(directoryGroup);
|
|
2392
|
+
const directoryHasActive = directoryContainsActiveSession(directoryGroup);
|
|
2393
|
+
const group = document.createElement('section');
|
|
2394
|
+
group.className = 'workbench-group';
|
|
2395
|
+
group.classList.toggle('has-active', directoryHasActive);
|
|
2396
|
+
|
|
2397
|
+
const groupHead = createTreeToggle({
|
|
2398
|
+
className: 'workbench-group-head',
|
|
2399
|
+
kickerClassName: 'workbench-group-kicker',
|
|
2400
|
+
titleClassName: 'workbench-group-title',
|
|
2401
|
+
kicker: '目录',
|
|
2402
|
+
title: directoryGroup.path,
|
|
2403
|
+
expanded: directoryExpanded,
|
|
2404
|
+
metaParts: [
|
|
2405
|
+
`${directoryGroup.containers.length} 个容器`,
|
|
2406
|
+
`${directoryGroup.containers.reduce(function (count, containerGroup) {
|
|
2407
|
+
return count + containerGroup.sessions.length;
|
|
2408
|
+
}, 0)} 个AGENT`,
|
|
2409
|
+
formatDateTime(directoryGroup.updatedAt) || '暂无更新'
|
|
2410
|
+
],
|
|
2411
|
+
onClick: function () {
|
|
2412
|
+
setSidebarDirectoryExpanded(directoryGroup.path, !directoryExpanded);
|
|
2413
|
+
renderSessions();
|
|
2414
|
+
}
|
|
2415
|
+
});
|
|
2416
|
+
group.appendChild(groupHead);
|
|
2417
|
+
|
|
2418
|
+
const containerStack = document.createElement('div');
|
|
2419
|
+
containerStack.className = 'container-stack workbench-group-body';
|
|
2420
|
+
containerStack.hidden = !directoryExpanded;
|
|
2421
|
+
|
|
2422
|
+
directoryGroup.containers.forEach(function (containerGroup) {
|
|
2423
|
+
const containerExpanded = isContainerExpanded(containerGroup);
|
|
2424
|
+
const containerHasActive = containerContainsActiveSession(containerGroup);
|
|
2425
|
+
const status = sessionStatusInfo(containerGroup.status);
|
|
2426
|
+
const containerCard = document.createElement('section');
|
|
2427
|
+
containerCard.className = 'container-card';
|
|
2428
|
+
containerCard.classList.toggle('has-active', containerHasActive);
|
|
2429
|
+
|
|
2430
|
+
const containerHead = document.createElement('div');
|
|
2431
|
+
containerHead.className = 'container-card-head';
|
|
2432
|
+
|
|
2433
|
+
const containerInfo = createTreeToggle({
|
|
2434
|
+
className: 'container-toggle',
|
|
2435
|
+
kickerClassName: 'container-card-kicker',
|
|
2436
|
+
titleClassName: 'container-card-title',
|
|
2437
|
+
kicker: '容器',
|
|
2438
|
+
title: containerGroup.containerName,
|
|
2439
|
+
expanded: containerExpanded,
|
|
2440
|
+
metaParts: [
|
|
2441
|
+
status.label,
|
|
2442
|
+
`${containerGroup.sessions.length} 个AGENT`,
|
|
2443
|
+
formatDateTime(containerGroup.updatedAt) || '暂无更新'
|
|
2444
|
+
],
|
|
2445
|
+
onClick: function () {
|
|
2446
|
+
setSidebarContainerExpanded(containerGroup.containerName, !containerExpanded);
|
|
2447
|
+
renderSessions();
|
|
2448
|
+
}
|
|
2449
|
+
});
|
|
1898
2450
|
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
2451
|
+
const addAgentBtn = document.createElement('button');
|
|
2452
|
+
addAgentBtn.type = 'button';
|
|
2453
|
+
addAgentBtn.className = 'secondary add-agent-btn';
|
|
2454
|
+
addAgentBtn.textContent = '新建AGENT';
|
|
2455
|
+
addAgentBtn.addEventListener('click', function () {
|
|
2456
|
+
createAgentSession(containerGroup.containerName);
|
|
2457
|
+
});
|
|
1904
2458
|
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
2459
|
+
containerHead.appendChild(containerInfo);
|
|
2460
|
+
containerHead.appendChild(addAgentBtn);
|
|
2461
|
+
containerCard.appendChild(containerHead);
|
|
2462
|
+
|
|
2463
|
+
const agentList = document.createElement('div');
|
|
2464
|
+
agentList.className = 'agent-list';
|
|
2465
|
+
agentList.hidden = !containerExpanded;
|
|
2466
|
+
containerGroup.sessions.forEach(function (session) {
|
|
2467
|
+
const row = createAgentRow(session, itemIndex);
|
|
2468
|
+
state.sessionNodeMap.set(session.name, row);
|
|
2469
|
+
agentList.appendChild(row);
|
|
2470
|
+
itemIndex += 1;
|
|
2471
|
+
});
|
|
2472
|
+
containerCard.appendChild(agentList);
|
|
2473
|
+
containerStack.appendChild(containerCard);
|
|
2474
|
+
});
|
|
2475
|
+
|
|
2476
|
+
group.appendChild(containerStack);
|
|
2477
|
+
sessionList.appendChild(group);
|
|
1917
2478
|
});
|
|
1918
2479
|
|
|
1919
|
-
|
|
1920
|
-
sessionList.removeChild(sessionList.lastChild);
|
|
1921
|
-
}
|
|
1922
|
-
renderSessionActiveState();
|
|
2480
|
+
scrollActiveSessionIntoView();
|
|
1923
2481
|
}
|
|
1924
2482
|
|
|
1925
2483
|
function renderMessagesLoading() {
|
|
@@ -1953,6 +2511,11 @@
|
|
|
1953
2511
|
}
|
|
1954
2512
|
|
|
1955
2513
|
function getMessageRenderKey(msg, index) {
|
|
2514
|
+
if (msg && msg.id && msg.streamingReply) {
|
|
2515
|
+
const content = msg.content ? String(msg.content) : '';
|
|
2516
|
+
const timestamp = msg.timestamp ? String(msg.timestamp) : '';
|
|
2517
|
+
return `id:${msg.id}|streaming|${timestamp}|${content}`;
|
|
2518
|
+
}
|
|
1956
2519
|
if (msg && msg.id && msg.streamTrace) {
|
|
1957
2520
|
const content = msg.content ? String(msg.content) : '';
|
|
1958
2521
|
const timestamp = msg.timestamp ? String(msg.timestamp) : '';
|
|
@@ -2004,14 +2567,39 @@
|
|
|
2004
2567
|
const bubble = document.createElement('div');
|
|
2005
2568
|
bubble.className = 'bubble';
|
|
2006
2569
|
|
|
2570
|
+
const isStreamingReply = Boolean(msg && msg.streamingReply);
|
|
2007
2571
|
const shouldRenderStructuredTrace = Boolean(
|
|
2008
2572
|
msg
|
|
2009
2573
|
&& msg.streamTrace
|
|
2010
2574
|
&& Array.isArray(msg.traceEvents)
|
|
2011
2575
|
&& msg.traceEvents.length
|
|
2012
2576
|
);
|
|
2013
|
-
const shouldRenderMarkdown = Boolean(!msg.streamTrace && markdownRenderer && markdownRenderer.shouldRenderMessage(msg));
|
|
2014
|
-
if (
|
|
2577
|
+
const shouldRenderMarkdown = Boolean(!msg.streamTrace && !msg.streamingReply && markdownRenderer && markdownRenderer.shouldRenderMessage(msg));
|
|
2578
|
+
if (isStreamingReply) {
|
|
2579
|
+
bubble.classList.add('streaming-reply');
|
|
2580
|
+
var replyContent = String(msg.content || '');
|
|
2581
|
+
if (replyContent && markdownRenderer && typeof markdownRenderer.render === 'function') {
|
|
2582
|
+
var mdNode = document.createElement('div');
|
|
2583
|
+
mdNode.className = 'md-content';
|
|
2584
|
+
var rendered = '';
|
|
2585
|
+
try {
|
|
2586
|
+
rendered = String(markdownRenderer.render(replyContent) || '');
|
|
2587
|
+
} catch (e) {
|
|
2588
|
+
rendered = '';
|
|
2589
|
+
}
|
|
2590
|
+
if (rendered) {
|
|
2591
|
+
mdNode.innerHTML = rendered;
|
|
2592
|
+
bubble.appendChild(mdNode);
|
|
2593
|
+
} else {
|
|
2594
|
+
appendPlainMessageContent(bubble, replyContent);
|
|
2595
|
+
}
|
|
2596
|
+
} else {
|
|
2597
|
+
appendPlainMessageContent(bubble, replyContent);
|
|
2598
|
+
}
|
|
2599
|
+
var cursor = document.createElement('span');
|
|
2600
|
+
cursor.className = 'streaming-cursor';
|
|
2601
|
+
bubble.appendChild(cursor);
|
|
2602
|
+
} else if (shouldRenderStructuredTrace) {
|
|
2015
2603
|
appendStructuredTraceContent(bubble, msg);
|
|
2016
2604
|
} else if (shouldRenderMarkdown) {
|
|
2017
2605
|
const markdownNode = document.createElement('div');
|
|
@@ -2102,7 +2690,9 @@
|
|
|
2102
2690
|
}
|
|
2103
2691
|
|
|
2104
2692
|
function applySessionsSnapshot(rawSessions, preferredName) {
|
|
2693
|
+
const previousActive = state.active;
|
|
2105
2694
|
state.sessions = Array.isArray(rawSessions) ? rawSessions : [];
|
|
2695
|
+
pruneSidebarTreeState();
|
|
2106
2696
|
|
|
2107
2697
|
if (typeof preferredName === 'string' && preferredName.trim()) {
|
|
2108
2698
|
state.active = preferredName.trim();
|
|
@@ -2116,6 +2706,10 @@
|
|
|
2116
2706
|
if (!state.active && state.sessions.length) {
|
|
2117
2707
|
state.active = state.sessions[0].name;
|
|
2118
2708
|
}
|
|
2709
|
+
if (state.active && state.active !== previousActive) {
|
|
2710
|
+
ensureSessionPathExpanded(state.active);
|
|
2711
|
+
state.pendingActiveSessionScroll = true;
|
|
2712
|
+
}
|
|
2119
2713
|
if (state.terminal.sessionName && state.terminal.sessionName !== state.active) {
|
|
2120
2714
|
disconnectTerminal('会话已变化,终端已断开', true);
|
|
2121
2715
|
}
|
|
@@ -2375,11 +2969,55 @@
|
|
|
2375
2969
|
});
|
|
2376
2970
|
}
|
|
2377
2971
|
|
|
2972
|
+
function appendStreamingReplyLocal(sessionName) {
|
|
2973
|
+
var replyMessage = {
|
|
2974
|
+
id: createLocalMessageId('local-streaming-reply'),
|
|
2975
|
+
role: 'assistant',
|
|
2976
|
+
content: '',
|
|
2977
|
+
timestamp: new Date().toISOString(),
|
|
2978
|
+
mode: 'agent',
|
|
2979
|
+
streamingReply: true
|
|
2980
|
+
};
|
|
2981
|
+
if (state.active === sessionName) {
|
|
2982
|
+
state.messages.push(replyMessage);
|
|
2983
|
+
}
|
|
2984
|
+
return replyMessage.id;
|
|
2985
|
+
}
|
|
2986
|
+
|
|
2987
|
+
function updateStreamingReplyLocal(sessionName, replyMessageId, content) {
|
|
2988
|
+
if (state.active !== sessionName) {
|
|
2989
|
+
return;
|
|
2990
|
+
}
|
|
2991
|
+
for (var i = state.messages.length - 1; i >= 0; i -= 1) {
|
|
2992
|
+
var message = state.messages[i];
|
|
2993
|
+
if (!message || String(message.id || '') !== String(replyMessageId || '')) {
|
|
2994
|
+
continue;
|
|
2995
|
+
}
|
|
2996
|
+
message.content = String(content || '');
|
|
2997
|
+
message.timestamp = new Date().toISOString();
|
|
2998
|
+
return;
|
|
2999
|
+
}
|
|
3000
|
+
}
|
|
3001
|
+
|
|
3002
|
+
function removeStreamingReplyLocal(sessionName, replyMessageId) {
|
|
3003
|
+
if (state.active !== sessionName) {
|
|
3004
|
+
return;
|
|
3005
|
+
}
|
|
3006
|
+
for (var i = state.messages.length - 1; i >= 0; i -= 1) {
|
|
3007
|
+
var message = state.messages[i];
|
|
3008
|
+
if (message && String(message.id || '') === String(replyMessageId || '') && message.streamingReply) {
|
|
3009
|
+
state.messages.splice(i, 1);
|
|
3010
|
+
return;
|
|
3011
|
+
}
|
|
3012
|
+
}
|
|
3013
|
+
}
|
|
3014
|
+
|
|
2378
3015
|
async function sendAgentPromptStream(sessionName, inputText, pendingMessage) {
|
|
2379
3016
|
const traceMessageId = appendAgentTraceMessageLocal(sessionName);
|
|
2380
3017
|
const traceLines = ['[执行过程]', '等待 Agent 启动…'];
|
|
2381
3018
|
let finalResult = null;
|
|
2382
3019
|
let streamError = null;
|
|
3020
|
+
let streamingReplyId = null;
|
|
2383
3021
|
|
|
2384
3022
|
state.agentRun.active = true;
|
|
2385
3023
|
state.agentRun.sessionName = sessionName;
|
|
@@ -2429,6 +3067,20 @@
|
|
|
2429
3067
|
pushTraceLine(event.text || '', event.traceEvent || null);
|
|
2430
3068
|
return;
|
|
2431
3069
|
}
|
|
3070
|
+
if (event.type === 'content_delta') {
|
|
3071
|
+
var content = String(event.content || '').trim();
|
|
3072
|
+
if (!content) {
|
|
3073
|
+
return;
|
|
3074
|
+
}
|
|
3075
|
+
if (!streamingReplyId) {
|
|
3076
|
+
streamingReplyId = appendStreamingReplyLocal(sessionName);
|
|
3077
|
+
}
|
|
3078
|
+
updateStreamingReplyLocal(sessionName, streamingReplyId, content);
|
|
3079
|
+
if (state.active === sessionName) {
|
|
3080
|
+
renderMessages(state.messages, { stickToBottom: true });
|
|
3081
|
+
}
|
|
3082
|
+
return;
|
|
3083
|
+
}
|
|
2432
3084
|
if (event.type === 'result') {
|
|
2433
3085
|
finalResult = event;
|
|
2434
3086
|
if (event.interrupted) {
|
|
@@ -2448,6 +3100,9 @@
|
|
|
2448
3100
|
streamError = e;
|
|
2449
3101
|
}
|
|
2450
3102
|
} finally {
|
|
3103
|
+
if (streamingReplyId) {
|
|
3104
|
+
removeStreamingReplyLocal(sessionName, streamingReplyId);
|
|
3105
|
+
}
|
|
2451
3106
|
const pendingIndex = confirmPendingUserMessage(sessionName, pendingMessage.id);
|
|
2452
3107
|
if (pendingIndex >= 0 && pendingIndex < state.messageRenderKeys.length) {
|
|
2453
3108
|
if (pendingIndex < messagesNode.children.length) {
|
|
@@ -2522,6 +3177,38 @@
|
|
|
2522
3177
|
});
|
|
2523
3178
|
}
|
|
2524
3179
|
|
|
3180
|
+
if (pickHostPathBtn) {
|
|
3181
|
+
pickHostPathBtn.addEventListener('click', function () {
|
|
3182
|
+
openDirectoryPicker('host');
|
|
3183
|
+
});
|
|
3184
|
+
}
|
|
3185
|
+
|
|
3186
|
+
if (pickContainerPathBtn) {
|
|
3187
|
+
pickContainerPathBtn.addEventListener('click', function () {
|
|
3188
|
+
openDirectoryPicker('container');
|
|
3189
|
+
});
|
|
3190
|
+
}
|
|
3191
|
+
|
|
3192
|
+
if (directoryPickerCancelBtn) {
|
|
3193
|
+
directoryPickerCancelBtn.addEventListener('click', function () {
|
|
3194
|
+
closeDirectoryPicker();
|
|
3195
|
+
});
|
|
3196
|
+
}
|
|
3197
|
+
|
|
3198
|
+
if (directoryPickerUpBtn) {
|
|
3199
|
+
directoryPickerUpBtn.addEventListener('click', function () {
|
|
3200
|
+
if (state.directoryPicker.parentPath) {
|
|
3201
|
+
loadDirectoryPicker(state.directoryPicker.parentPath);
|
|
3202
|
+
}
|
|
3203
|
+
});
|
|
3204
|
+
}
|
|
3205
|
+
|
|
3206
|
+
if (directoryPickerSelectBtn) {
|
|
3207
|
+
directoryPickerSelectBtn.addEventListener('click', function () {
|
|
3208
|
+
applyPickedDirectory();
|
|
3209
|
+
});
|
|
3210
|
+
}
|
|
3211
|
+
|
|
2525
3212
|
if (createRun) {
|
|
2526
3213
|
createRun.addEventListener('change', function () {
|
|
2527
3214
|
applyCurrentRunDefaults();
|
|
@@ -2804,6 +3491,18 @@
|
|
|
2804
3491
|
});
|
|
2805
3492
|
}
|
|
2806
3493
|
|
|
3494
|
+
if (addAgentBtn) {
|
|
3495
|
+
addAgentBtn.addEventListener('click', function () {
|
|
3496
|
+
const activeSession = getActiveSession();
|
|
3497
|
+
const targetContainer = activeSession ? getSessionContainerName(activeSession) : '';
|
|
3498
|
+
if (!targetContainer) {
|
|
3499
|
+
return;
|
|
3500
|
+
}
|
|
3501
|
+
closeMobileActionsMenu();
|
|
3502
|
+
createAgentSession(targetContainer);
|
|
3503
|
+
});
|
|
3504
|
+
}
|
|
3505
|
+
|
|
2807
3506
|
if (configModal) {
|
|
2808
3507
|
configModal.addEventListener('click', function (event) {
|
|
2809
3508
|
if (event.target === configModal && !state.configSaving) {
|
|
@@ -2822,6 +3521,14 @@
|
|
|
2822
3521
|
});
|
|
2823
3522
|
}
|
|
2824
3523
|
|
|
3524
|
+
if (directoryPickerModal) {
|
|
3525
|
+
directoryPickerModal.addEventListener('click', function (event) {
|
|
3526
|
+
if (event.target === directoryPickerModal && !state.directoryPicker.loading) {
|
|
3527
|
+
closeDirectoryPicker();
|
|
3528
|
+
}
|
|
3529
|
+
});
|
|
3530
|
+
}
|
|
3531
|
+
|
|
2825
3532
|
window.addEventListener('keydown', function (event) {
|
|
2826
3533
|
if (event.key === 'Escape' && state.configModalOpen) {
|
|
2827
3534
|
closeConfigModal();
|
|
@@ -2831,6 +3538,9 @@
|
|
|
2831
3538
|
closeCreateModal();
|
|
2832
3539
|
syncUi();
|
|
2833
3540
|
}
|
|
3541
|
+
if (event.key === 'Escape' && state.directoryPicker.open) {
|
|
3542
|
+
closeDirectoryPicker();
|
|
3543
|
+
}
|
|
2834
3544
|
if (event.key === 'Escape' && state.mobileSidebarOpen) {
|
|
2835
3545
|
closeMobileSessionPanel();
|
|
2836
3546
|
}
|
|
@@ -2877,7 +3587,9 @@
|
|
|
2877
3587
|
removeBtn.addEventListener('click', async function () {
|
|
2878
3588
|
if (!state.active) return;
|
|
2879
3589
|
closeMobileActionsMenu();
|
|
2880
|
-
const
|
|
3590
|
+
const activeSession = getActiveSession();
|
|
3591
|
+
const targetContainer = activeSession && activeSession.containerName ? activeSession.containerName : state.active;
|
|
3592
|
+
const yes = confirm('确认删除容器 ' + targetContainer + ' ?');
|
|
2881
3593
|
if (!yes) return;
|
|
2882
3594
|
try {
|
|
2883
3595
|
const current = state.active;
|
|
@@ -2896,7 +3608,9 @@
|
|
|
2896
3608
|
removeAllBtn.addEventListener('click', async function () {
|
|
2897
3609
|
if (!state.active) return;
|
|
2898
3610
|
closeMobileActionsMenu();
|
|
2899
|
-
const
|
|
3611
|
+
const activeSession = getActiveSession();
|
|
3612
|
+
const targetAgent = activeSession && activeSession.agentName ? activeSession.agentName : state.active;
|
|
3613
|
+
const yes = confirm('确认删除对话 ' + targetAgent + ' ?');
|
|
2900
3614
|
if (!yes) return;
|
|
2901
3615
|
try {
|
|
2902
3616
|
const current = state.active;
|
|
@@ -2913,6 +3627,7 @@
|
|
|
2913
3627
|
disconnectTerminal('', true);
|
|
2914
3628
|
});
|
|
2915
3629
|
|
|
3630
|
+
loadSidebarTreeState();
|
|
2916
3631
|
renderSessions();
|
|
2917
3632
|
renderMessages(state.messages);
|
|
2918
3633
|
setMobileSessionPanel(false);
|