@xcanwin/manyoyo 6.2.4 → 6.2.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 +94 -137
- package/lib/web/frontend/app.html +28 -1
- package/lib/web/frontend/app.js +498 -401
- package/lib/web/server.js +290 -1
- package/package.json +1 -1
package/lib/web/frontend/app.js
CHANGED
|
@@ -71,10 +71,17 @@
|
|
|
71
71
|
createRuns: {},
|
|
72
72
|
sessionNodeMap: new Map(),
|
|
73
73
|
sessionRenderMode: 'empty',
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
74
|
+
sidebarNavLoaded: false,
|
|
75
|
+
navLevel: 'containers',
|
|
76
|
+
navContainer: '',
|
|
77
|
+
containerSearchQuery: '',
|
|
78
|
+
cloneModal: {
|
|
79
|
+
open: false,
|
|
80
|
+
mode: '',
|
|
81
|
+
sourceContainerName: '',
|
|
82
|
+
nameDraft: '',
|
|
83
|
+
submitting: false,
|
|
84
|
+
error: ''
|
|
78
85
|
},
|
|
79
86
|
pendingActiveSessionScroll: false,
|
|
80
87
|
directoryPicker: {
|
|
@@ -120,6 +127,8 @@
|
|
|
120
127
|
};
|
|
121
128
|
|
|
122
129
|
const sidebarNode = document.querySelector('.sidebar');
|
|
130
|
+
const sessionBreadcrumb = document.getElementById('sessionBreadcrumb');
|
|
131
|
+
const containerSearchInput = document.getElementById('containerSearchInput');
|
|
123
132
|
const sessionList = document.getElementById('sessionList');
|
|
124
133
|
const sessionCount = document.getElementById('sessionCount');
|
|
125
134
|
const mobileSessionToggle = document.getElementById('mobileSessionToggle');
|
|
@@ -211,6 +220,13 @@
|
|
|
211
220
|
const externalLinkUrl = document.getElementById('externalLinkUrl');
|
|
212
221
|
const externalLinkCancelBtn = document.getElementById('externalLinkCancelBtn');
|
|
213
222
|
const externalLinkOpenBtn = document.getElementById('externalLinkOpenBtn');
|
|
223
|
+
const cloneNameModal = document.getElementById('cloneNameModal');
|
|
224
|
+
const cloneNameTitle = document.getElementById('cloneNameTitle');
|
|
225
|
+
const cloneNameTip = document.getElementById('cloneNameTip');
|
|
226
|
+
const cloneNameInput = document.getElementById('cloneNameInput');
|
|
227
|
+
const cloneNameError = document.getElementById('cloneNameError');
|
|
228
|
+
const cloneNameCancelBtn = document.getElementById('cloneNameCancelBtn');
|
|
229
|
+
const cloneNameConfirmBtn = document.getElementById('cloneNameConfirmBtn');
|
|
214
230
|
const MOBILE_LAYOUT_MEDIA = window.matchMedia('(max-width: 980px)');
|
|
215
231
|
const MOBILE_COMPACT_MEDIA = window.matchMedia('(max-width: 640px)');
|
|
216
232
|
const TERMINAL_FIT_DEBOUNCE_MS = 60;
|
|
@@ -248,58 +264,49 @@
|
|
|
248
264
|
gemini: YOLO_COMMAND_MAP.gemini,
|
|
249
265
|
opencode: YOLO_COMMAND_MAP.opencode
|
|
250
266
|
};
|
|
251
|
-
const
|
|
267
|
+
const SIDEBAR_NAV_STORAGE_KEY = 'manyoyo.web.sidebarNav.v1';
|
|
252
268
|
const markdownRenderer = window.ManyoyoMarkdown
|
|
253
269
|
&& typeof window.ManyoyoMarkdown.shouldRenderMessage === 'function'
|
|
254
270
|
&& typeof window.ManyoyoMarkdown.render === 'function'
|
|
255
271
|
? window.ManyoyoMarkdown
|
|
256
272
|
: null;
|
|
257
273
|
let fileBrowser = null;
|
|
258
|
-
function normalizeBooleanMap(source) {
|
|
259
|
-
const result = {};
|
|
260
|
-
if (!source || typeof source !== 'object' || Array.isArray(source)) {
|
|
261
|
-
return result;
|
|
262
|
-
}
|
|
263
|
-
Object.keys(source).forEach(function (key) {
|
|
264
|
-
if (typeof source[key] === 'boolean') {
|
|
265
|
-
result[String(key)] = source[key];
|
|
266
|
-
}
|
|
267
|
-
});
|
|
268
|
-
return result;
|
|
269
|
-
}
|
|
270
274
|
|
|
271
|
-
function
|
|
272
|
-
if (state.
|
|
275
|
+
function loadSidebarNavState() {
|
|
276
|
+
if (state.sidebarNavLoaded) {
|
|
273
277
|
return;
|
|
274
278
|
}
|
|
275
|
-
state.
|
|
279
|
+
state.sidebarNavLoaded = true;
|
|
276
280
|
try {
|
|
277
281
|
if (!window.localStorage) {
|
|
278
282
|
return;
|
|
279
283
|
}
|
|
280
|
-
const raw = window.localStorage.getItem(
|
|
284
|
+
const raw = window.localStorage.getItem(SIDEBAR_NAV_STORAGE_KEY);
|
|
281
285
|
if (!raw) {
|
|
282
286
|
return;
|
|
283
287
|
}
|
|
284
288
|
const parsed = JSON.parse(raw);
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
+
if (parsed && (parsed.navLevel === 'agents' || parsed.navLevel === 'containers')) {
|
|
290
|
+
state.navLevel = parsed.navLevel;
|
|
291
|
+
}
|
|
292
|
+
if (parsed && typeof parsed.navContainer === 'string') {
|
|
293
|
+
state.navContainer = parsed.navContainer;
|
|
294
|
+
}
|
|
289
295
|
} catch (e) {
|
|
290
|
-
state.
|
|
291
|
-
|
|
292
|
-
containers: {}
|
|
293
|
-
};
|
|
296
|
+
state.navLevel = 'containers';
|
|
297
|
+
state.navContainer = '';
|
|
294
298
|
}
|
|
295
299
|
}
|
|
296
300
|
|
|
297
|
-
function
|
|
301
|
+
function persistSidebarNavState() {
|
|
298
302
|
try {
|
|
299
303
|
if (!window.localStorage) {
|
|
300
304
|
return;
|
|
301
305
|
}
|
|
302
|
-
window.localStorage.setItem(
|
|
306
|
+
window.localStorage.setItem(SIDEBAR_NAV_STORAGE_KEY, JSON.stringify({
|
|
307
|
+
navLevel: state.navLevel,
|
|
308
|
+
navContainer: state.navContainer
|
|
309
|
+
}));
|
|
303
310
|
} catch (e) {
|
|
304
311
|
// 忽略浏览器存储异常,避免影响主流程
|
|
305
312
|
}
|
|
@@ -686,87 +693,33 @@
|
|
|
686
693
|
}) || null;
|
|
687
694
|
}
|
|
688
695
|
|
|
689
|
-
function
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
const validContainers = new Set(state.sessions.map(getSessionContainerName).filter(Boolean));
|
|
694
|
-
let changed = false;
|
|
695
|
-
|
|
696
|
-
Object.keys(state.sidebarTree.directories).forEach(function (key) {
|
|
697
|
-
if (!validDirectories.has(key)) {
|
|
698
|
-
delete state.sidebarTree.directories[key];
|
|
699
|
-
changed = true;
|
|
700
|
-
}
|
|
701
|
-
});
|
|
702
|
-
|
|
703
|
-
Object.keys(state.sidebarTree.containers).forEach(function (key) {
|
|
704
|
-
if (!validContainers.has(key)) {
|
|
705
|
-
delete state.sidebarTree.containers[key];
|
|
706
|
-
changed = true;
|
|
707
|
-
}
|
|
708
|
-
});
|
|
709
|
-
|
|
710
|
-
if (changed) {
|
|
711
|
-
persistSidebarTreeState();
|
|
712
|
-
}
|
|
713
|
-
}
|
|
714
|
-
|
|
715
|
-
function setSidebarDirectoryExpanded(directoryPath, expanded, options) {
|
|
716
|
-
const path = String(directoryPath || '').trim();
|
|
717
|
-
if (!path) {
|
|
718
|
-
return false;
|
|
719
|
-
}
|
|
720
|
-
loadSidebarTreeState();
|
|
721
|
-
const opts = options && typeof options === 'object' ? options : {};
|
|
722
|
-
if (state.sidebarTree.directories[path] === expanded) {
|
|
723
|
-
return false;
|
|
724
|
-
}
|
|
725
|
-
state.sidebarTree.directories[path] = expanded;
|
|
726
|
-
if (opts.persist !== false) {
|
|
727
|
-
persistSidebarTreeState();
|
|
728
|
-
}
|
|
729
|
-
return true;
|
|
730
|
-
}
|
|
731
|
-
|
|
732
|
-
function setSidebarContainerExpanded(containerName, expanded, options) {
|
|
733
|
-
const name = String(containerName || '').trim();
|
|
734
|
-
if (!name) {
|
|
735
|
-
return false;
|
|
736
|
-
}
|
|
737
|
-
loadSidebarTreeState();
|
|
738
|
-
const opts = options && typeof options === 'object' ? options : {};
|
|
739
|
-
if (state.sidebarTree.containers[name] === expanded) {
|
|
740
|
-
return false;
|
|
696
|
+
function pruneSidebarNavState() {
|
|
697
|
+
loadSidebarNavState();
|
|
698
|
+
if (state.navLevel !== 'agents') {
|
|
699
|
+
return;
|
|
741
700
|
}
|
|
742
|
-
state.
|
|
743
|
-
if (
|
|
744
|
-
|
|
701
|
+
const validContainers = new Set(state.sessions.map(getSessionContainerName).filter(Boolean));
|
|
702
|
+
if (!validContainers.has(state.navContainer)) {
|
|
703
|
+
state.navLevel = 'containers';
|
|
704
|
+
state.navContainer = '';
|
|
705
|
+
persistSidebarNavState();
|
|
745
706
|
}
|
|
746
|
-
return true;
|
|
747
707
|
}
|
|
748
708
|
|
|
749
|
-
function
|
|
709
|
+
function ensureSessionNavPath(sessionName, options) {
|
|
750
710
|
const session = findSessionByName(sessionName);
|
|
751
711
|
if (!session) {
|
|
752
712
|
return false;
|
|
753
713
|
}
|
|
754
714
|
const opts = options && typeof options === 'object' ? options : {};
|
|
755
|
-
const directoryPath = getSessionDirectoryPath(session);
|
|
756
715
|
const containerName = getSessionContainerName(session);
|
|
757
|
-
const
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
716
|
+
const changed = state.navLevel !== 'agents' || state.navContainer !== containerName;
|
|
717
|
+
state.navLevel = 'agents';
|
|
718
|
+
state.navContainer = containerName;
|
|
719
|
+
if (changed && opts.persist !== false) {
|
|
720
|
+
persistSidebarNavState();
|
|
761
721
|
}
|
|
762
|
-
return
|
|
763
|
-
}
|
|
764
|
-
|
|
765
|
-
function directoryContainsActiveSession(directoryGroup) {
|
|
766
|
-
const groups = directoryGroup && Array.isArray(directoryGroup.containers) ? directoryGroup.containers : [];
|
|
767
|
-
return groups.some(function (containerGroup) {
|
|
768
|
-
return containerContainsActiveSession(containerGroup);
|
|
769
|
-
});
|
|
722
|
+
return changed;
|
|
770
723
|
}
|
|
771
724
|
|
|
772
725
|
function containerContainsActiveSession(containerGroup) {
|
|
@@ -776,24 +729,6 @@
|
|
|
776
729
|
});
|
|
777
730
|
}
|
|
778
731
|
|
|
779
|
-
function isDirectoryExpanded(directoryGroup) {
|
|
780
|
-
loadSidebarTreeState();
|
|
781
|
-
const key = String(directoryGroup && directoryGroup.path ? directoryGroup.path : '').trim();
|
|
782
|
-
if (key && typeof state.sidebarTree.directories[key] === 'boolean') {
|
|
783
|
-
return state.sidebarTree.directories[key];
|
|
784
|
-
}
|
|
785
|
-
return false;
|
|
786
|
-
}
|
|
787
|
-
|
|
788
|
-
function isContainerExpanded(containerGroup) {
|
|
789
|
-
loadSidebarTreeState();
|
|
790
|
-
const key = String(containerGroup && containerGroup.containerName ? containerGroup.containerName : '').trim();
|
|
791
|
-
if (key && typeof state.sidebarTree.containers[key] === 'boolean') {
|
|
792
|
-
return state.sidebarTree.containers[key];
|
|
793
|
-
}
|
|
794
|
-
return false;
|
|
795
|
-
}
|
|
796
|
-
|
|
797
732
|
function setModalVisible(modalNode, visible) {
|
|
798
733
|
if (!modalNode) return;
|
|
799
734
|
modalNode.hidden = !visible;
|
|
@@ -1414,6 +1349,9 @@
|
|
|
1414
1349
|
state.agentTemplateModalOpen = true;
|
|
1415
1350
|
fillAgentTemplateForm(detail);
|
|
1416
1351
|
syncUi();
|
|
1352
|
+
if (isMobileLayout()) {
|
|
1353
|
+
return;
|
|
1354
|
+
}
|
|
1417
1355
|
if (isActiveAgentOverrideEditable() && agentCliSelect) {
|
|
1418
1356
|
agentCliSelect.focus();
|
|
1419
1357
|
} else if (containerCliSelect) {
|
|
@@ -1861,7 +1799,9 @@
|
|
|
1861
1799
|
state.terminal.connected = true;
|
|
1862
1800
|
state.terminal.status = '已连接';
|
|
1863
1801
|
if (state.terminal.term) {
|
|
1864
|
-
|
|
1802
|
+
if (!isMobileLayout()) {
|
|
1803
|
+
state.terminal.term.focus();
|
|
1804
|
+
}
|
|
1865
1805
|
scheduleTerminalFit(true);
|
|
1866
1806
|
}
|
|
1867
1807
|
syncUi();
|
|
@@ -1999,7 +1939,7 @@
|
|
|
1999
1939
|
if (!state.terminal.connected && !state.terminal.connecting && !isActiveSessionHistoryOnly()) {
|
|
2000
1940
|
connectTerminal();
|
|
2001
1941
|
}
|
|
2002
|
-
if (state.terminal.term) {
|
|
1942
|
+
if (state.terminal.term && !isMobileLayout()) {
|
|
2003
1943
|
state.terminal.term.focus();
|
|
2004
1944
|
}
|
|
2005
1945
|
}
|
|
@@ -2423,7 +2363,7 @@
|
|
|
2423
2363
|
}
|
|
2424
2364
|
document.body.classList.toggle(
|
|
2425
2365
|
'modal-open',
|
|
2426
|
-
state.configModalOpen || state.createModalOpen || state.directoryPicker.open || state.agentTemplateModalOpen || state.externalLinkModalOpen
|
|
2366
|
+
state.configModalOpen || state.createModalOpen || state.directoryPicker.open || state.agentTemplateModalOpen || state.externalLinkModalOpen || state.cloneModal.open
|
|
2427
2367
|
);
|
|
2428
2368
|
if (composer) {
|
|
2429
2369
|
composer.hidden = !activityTab;
|
|
@@ -2839,7 +2779,7 @@
|
|
|
2839
2779
|
disconnectTerminal('会话切换,终端已断开', true);
|
|
2840
2780
|
}
|
|
2841
2781
|
state.active = sessionName;
|
|
2842
|
-
|
|
2782
|
+
ensureSessionNavPath(sessionName);
|
|
2843
2783
|
state.sessionDetail = null;
|
|
2844
2784
|
state.sessionDetailError = '';
|
|
2845
2785
|
if (isMobileLayout()) {
|
|
@@ -2894,48 +2834,33 @@
|
|
|
2894
2834
|
}
|
|
2895
2835
|
}
|
|
2896
2836
|
|
|
2897
|
-
function
|
|
2837
|
+
function groupSessionsByContainer(sessions) {
|
|
2898
2838
|
const groups = new Map();
|
|
2899
2839
|
(Array.isArray(sessions) ? sessions : []).forEach(function (session) {
|
|
2900
|
-
const
|
|
2901
|
-
if (!
|
|
2902
|
-
|
|
2903
|
-
path: directoryPath,
|
|
2904
|
-
updatedAt: session && session.updatedAt ? session.updatedAt : '',
|
|
2905
|
-
containers: new Map()
|
|
2906
|
-
});
|
|
2907
|
-
}
|
|
2908
|
-
const directoryGroup = groups.get(directoryPath);
|
|
2909
|
-
if (session && session.updatedAt && (!directoryGroup.updatedAt || new Date(session.updatedAt).getTime() > new Date(directoryGroup.updatedAt).getTime())) {
|
|
2910
|
-
directoryGroup.updatedAt = session.updatedAt;
|
|
2840
|
+
const containerName = getSessionContainerName(session);
|
|
2841
|
+
if (!containerName) {
|
|
2842
|
+
return;
|
|
2911
2843
|
}
|
|
2912
|
-
|
|
2913
|
-
|
|
2914
|
-
if (!directoryGroup.containers.has(containerName)) {
|
|
2915
|
-
directoryGroup.containers.set(containerName, {
|
|
2844
|
+
if (!groups.has(containerName)) {
|
|
2845
|
+
groups.set(containerName, {
|
|
2916
2846
|
containerName: containerName,
|
|
2847
|
+
containerRemark: session && session.containerRemark ? session.containerRemark : '',
|
|
2917
2848
|
status: session && session.status ? session.status : 'history',
|
|
2918
2849
|
image: session && session.image ? session.image : '',
|
|
2850
|
+
hostPath: getSessionDirectoryPath(session),
|
|
2919
2851
|
updatedAt: session && session.updatedAt ? session.updatedAt : '',
|
|
2920
2852
|
sessions: []
|
|
2921
2853
|
});
|
|
2922
2854
|
}
|
|
2923
|
-
const containerGroup =
|
|
2855
|
+
const containerGroup = groups.get(containerName);
|
|
2924
2856
|
containerGroup.sessions.push(session);
|
|
2925
2857
|
if (session && session.updatedAt && (!containerGroup.updatedAt || new Date(session.updatedAt).getTime() > new Date(containerGroup.updatedAt).getTime())) {
|
|
2926
2858
|
containerGroup.updatedAt = session.updatedAt;
|
|
2927
2859
|
}
|
|
2928
2860
|
});
|
|
2929
2861
|
return Array.from(groups.values()).map(function (group) {
|
|
2930
|
-
group.
|
|
2931
|
-
|
|
2932
|
-
const timeB = b.updatedAt ? new Date(b.updatedAt).getTime() : 0;
|
|
2933
|
-
return timeB - timeA;
|
|
2934
|
-
});
|
|
2935
|
-
group.containers.forEach(function (containerGroup) {
|
|
2936
|
-
containerGroup.sessions.sort(function (a, b) {
|
|
2937
|
-
return compareSessionByCreatedDesc(a, b);
|
|
2938
|
-
});
|
|
2862
|
+
group.sessions.sort(function (a, b) {
|
|
2863
|
+
return compareSessionByCreatedDesc(a, b);
|
|
2939
2864
|
});
|
|
2940
2865
|
return group;
|
|
2941
2866
|
}).sort(function (a, b) {
|
|
@@ -3163,254 +3088,272 @@
|
|
|
3163
3088
|
}
|
|
3164
3089
|
}
|
|
3165
3090
|
|
|
3166
|
-
function
|
|
3167
|
-
const button = document.createElement('button');
|
|
3168
|
-
button.type = 'button';
|
|
3169
|
-
button.className = 'disclosure-toggle';
|
|
3170
|
-
button.dataset.disclosureLabel = label || '';
|
|
3171
|
-
button.setAttribute('aria-expanded', expanded ? 'true' : 'false');
|
|
3172
|
-
button.setAttribute('aria-label', `${expanded ? '折叠' : '展开'}${label ? ` ${label}` : ''}`);
|
|
3173
|
-
button.innerHTML = '<svg viewBox="0 0 12 12" aria-hidden="true" focusable="false"><path d="M4 2.5L8 6L4 9.5"></path></svg>';
|
|
3174
|
-
return button;
|
|
3175
|
-
}
|
|
3176
|
-
|
|
3177
|
-
function createTreePrefixSegment() {
|
|
3178
|
-
const segment = document.createElement('span');
|
|
3179
|
-
segment.className = 'tree-prefix-segment';
|
|
3180
|
-
segment.setAttribute('aria-hidden', 'true');
|
|
3181
|
-
return segment;
|
|
3182
|
-
}
|
|
3183
|
-
|
|
3184
|
-
function createTreePrefix(ancestorHasNext, isLastSibling, options) {
|
|
3091
|
+
function createSessionCard(options) {
|
|
3185
3092
|
const opts = options && typeof options === 'object' ? options : {};
|
|
3186
|
-
const
|
|
3187
|
-
|
|
3188
|
-
|
|
3189
|
-
(Array.isArray(ancestorHasNext) ? ancestorHasNext : []).forEach(function () {
|
|
3190
|
-
prefix.appendChild(createTreePrefixSegment());
|
|
3191
|
-
});
|
|
3192
|
-
|
|
3193
|
-
const branch = document.createElement('span');
|
|
3194
|
-
branch.className = `tree-prefix-branch ${isLastSibling ? 'is-last' : 'is-mid'}`;
|
|
3195
|
-
branch.setAttribute('aria-hidden', 'true');
|
|
3196
|
-
prefix.appendChild(branch);
|
|
3197
|
-
|
|
3198
|
-
let disclosure = null;
|
|
3199
|
-
let disclosureWrap = null;
|
|
3200
|
-
if (opts.expandable) {
|
|
3201
|
-
disclosure = createDisclosureButton(!!opts.expanded, opts.label || '');
|
|
3202
|
-
disclosureWrap = document.createElement('span');
|
|
3203
|
-
disclosureWrap.className = 'tree-prefix-toggle';
|
|
3204
|
-
disclosureWrap.appendChild(disclosure);
|
|
3205
|
-
prefix.appendChild(disclosureWrap);
|
|
3206
|
-
} else {
|
|
3207
|
-
const leaf = document.createElement('span');
|
|
3208
|
-
leaf.className = 'tree-prefix-leaf';
|
|
3209
|
-
leaf.setAttribute('aria-hidden', 'true');
|
|
3210
|
-
prefix.appendChild(leaf);
|
|
3211
|
-
}
|
|
3212
|
-
|
|
3213
|
-
return { root: prefix, disclosure, disclosureWrap };
|
|
3214
|
-
}
|
|
3215
|
-
|
|
3216
|
-
function setDisclosureExpanded(control, expanded) {
|
|
3217
|
-
if (!control) {
|
|
3218
|
-
return;
|
|
3219
|
-
}
|
|
3220
|
-
const nextValue = expanded ? 'true' : 'false';
|
|
3221
|
-
if (control.button) {
|
|
3222
|
-
control.button.setAttribute('aria-expanded', nextValue);
|
|
3223
|
-
}
|
|
3224
|
-
if (control.disclosure) {
|
|
3225
|
-
control.disclosure.setAttribute('aria-expanded', nextValue);
|
|
3226
|
-
const label = control.disclosure.dataset.disclosureLabel || '';
|
|
3227
|
-
control.disclosure.setAttribute('aria-label', `${expanded ? '折叠' : '展开'}${label ? ` ${label}` : ''}`);
|
|
3228
|
-
}
|
|
3229
|
-
}
|
|
3230
|
-
|
|
3231
|
-
function createTreeItem(options) {
|
|
3232
|
-
const opts = options && typeof options === 'object' ? options : {};
|
|
3233
|
-
const row = document.createElement('div');
|
|
3234
|
-
row.className = `tree-node-row tree-node-row-${opts.kind || 'item'} ${opts.rowClassName || ''}`.trim();
|
|
3235
|
-
row.classList.toggle('has-active', !!opts.hasActive);
|
|
3236
|
-
row.setAttribute('role', 'none');
|
|
3237
|
-
|
|
3238
|
-
const prefixControl = createTreePrefix(opts.ancestorHasNext, !!opts.isLastSibling, {
|
|
3239
|
-
expandable: !!opts.expandable,
|
|
3240
|
-
expanded: !!opts.expanded,
|
|
3241
|
-
label: opts.disclosureLabel || opts.title || ''
|
|
3242
|
-
});
|
|
3243
|
-
row.appendChild(prefixControl.root);
|
|
3093
|
+
const card = document.createElement('div');
|
|
3094
|
+
card.className = `session-card session-card-${opts.kind || 'item'} ${opts.className || ''}`.trim();
|
|
3095
|
+
card.classList.toggle('has-active', !!opts.hasActive);
|
|
3244
3096
|
|
|
3245
3097
|
const button = document.createElement('button');
|
|
3246
3098
|
button.type = 'button';
|
|
3247
|
-
button.className =
|
|
3248
|
-
button.
|
|
3249
|
-
button.setAttribute('aria-level', String(opts.level || 1));
|
|
3250
|
-
if (opts.expandable) {
|
|
3251
|
-
button.setAttribute('aria-expanded', opts.expanded ? 'true' : 'false');
|
|
3252
|
-
}
|
|
3099
|
+
button.className = 'session-card-button';
|
|
3100
|
+
button.classList.toggle('active', !!opts.active);
|
|
3253
3101
|
if (opts.kind === 'agent') {
|
|
3254
3102
|
button.setAttribute('aria-selected', opts.active ? 'true' : 'false');
|
|
3255
3103
|
}
|
|
3256
|
-
button.
|
|
3104
|
+
button.addEventListener('click', function () {
|
|
3105
|
+
if (typeof opts.onClick === 'function') {
|
|
3106
|
+
opts.onClick();
|
|
3107
|
+
}
|
|
3108
|
+
});
|
|
3257
3109
|
|
|
3258
3110
|
const main = document.createElement('div');
|
|
3259
|
-
main.className = '
|
|
3111
|
+
main.className = 'session-card-main';
|
|
3260
3112
|
|
|
3261
|
-
const
|
|
3262
|
-
|
|
3113
|
+
const titleRow = document.createElement('div');
|
|
3114
|
+
titleRow.className = 'session-card-title-row';
|
|
3115
|
+
const title = document.createElement('span');
|
|
3116
|
+
title.className = 'session-card-title';
|
|
3263
3117
|
title.textContent = opts.title || '';
|
|
3264
3118
|
title.title = opts.title || '';
|
|
3265
|
-
|
|
3119
|
+
titleRow.appendChild(title);
|
|
3120
|
+
if (opts.statusLabel) {
|
|
3121
|
+
const statusBadge = document.createElement('span');
|
|
3122
|
+
statusBadge.className = `tree-node-status ${opts.statusTone || 'unknown'}`;
|
|
3123
|
+
statusBadge.textContent = opts.statusLabel;
|
|
3124
|
+
titleRow.appendChild(statusBadge);
|
|
3125
|
+
}
|
|
3126
|
+
main.appendChild(titleRow);
|
|
3266
3127
|
|
|
3267
3128
|
if (opts.meta) {
|
|
3268
3129
|
const meta = document.createElement('div');
|
|
3269
|
-
meta.className =
|
|
3130
|
+
meta.className = 'session-card-meta';
|
|
3270
3131
|
meta.textContent = opts.meta;
|
|
3271
3132
|
main.appendChild(meta);
|
|
3272
3133
|
}
|
|
3134
|
+
if (opts.subMeta) {
|
|
3135
|
+
const subMeta = document.createElement('div');
|
|
3136
|
+
subMeta.className = 'session-card-submeta';
|
|
3137
|
+
subMeta.textContent = opts.subMeta;
|
|
3138
|
+
subMeta.title = opts.subMeta;
|
|
3139
|
+
main.appendChild(subMeta);
|
|
3140
|
+
}
|
|
3273
3141
|
|
|
3274
3142
|
button.appendChild(main);
|
|
3275
|
-
|
|
3143
|
+
card.appendChild(button);
|
|
3276
3144
|
if (opts.overlayNode) {
|
|
3277
|
-
|
|
3145
|
+
card.appendChild(opts.overlayNode);
|
|
3278
3146
|
}
|
|
3279
|
-
|
|
3280
|
-
const control = {
|
|
3281
|
-
root: row,
|
|
3282
|
-
button,
|
|
3283
|
-
disclosure: prefixControl.disclosure
|
|
3284
|
-
};
|
|
3285
|
-
setDisclosureExpanded(control, !!opts.expanded);
|
|
3286
|
-
return control;
|
|
3147
|
+
return { root: card, button };
|
|
3287
3148
|
}
|
|
3288
3149
|
|
|
3289
|
-
function
|
|
3290
|
-
|
|
3291
|
-
|
|
3292
|
-
|
|
3293
|
-
|
|
3294
|
-
|
|
3295
|
-
|
|
3296
|
-
|
|
3297
|
-
|
|
3298
|
-
|
|
3299
|
-
|
|
3300
|
-
|
|
3301
|
-
|
|
3302
|
-
|
|
3303
|
-
|
|
3304
|
-
const containerMenu = createTreeNodeMenu([
|
|
3305
|
-
{
|
|
3306
|
-
label: '新建 AGENT',
|
|
3307
|
-
onClick: function () {
|
|
3308
|
-
createAgentSession(containerName);
|
|
3309
|
-
}
|
|
3310
|
-
},
|
|
3311
|
-
{
|
|
3312
|
-
label: '删除容器',
|
|
3313
|
-
danger: true,
|
|
3314
|
-
onClick: function () {
|
|
3315
|
-
removeContainerByName(containerName);
|
|
3316
|
-
}
|
|
3317
|
-
}
|
|
3318
|
-
]);
|
|
3319
|
-
|
|
3320
|
-
return {
|
|
3321
|
-
kind: 'container',
|
|
3322
|
-
title: containerName,
|
|
3323
|
-
meta: status.label,
|
|
3324
|
-
metaClassName: `tree-node-status ${status.tone}`,
|
|
3325
|
-
className: historyClassName,
|
|
3326
|
-
disclosureLabel: containerName,
|
|
3327
|
-
expanded: isContainerExpanded(containerGroup),
|
|
3328
|
-
hasActive: containerContainsActiveSession(containerGroup),
|
|
3329
|
-
overlayNode: containerMenu,
|
|
3330
|
-
onToggle: function (expanded) {
|
|
3331
|
-
setSidebarContainerExpanded(containerGroup.containerName, expanded);
|
|
3332
|
-
},
|
|
3333
|
-
children: (Array.isArray(containerGroup.sessions) ? containerGroup.sessions : []).map(function (session) {
|
|
3334
|
-
const agentTitle = session.agentName || session.name;
|
|
3335
|
-
return {
|
|
3336
|
-
kind: 'agent',
|
|
3337
|
-
title: agentTitle,
|
|
3338
|
-
meta: formatDateTime(session.updatedAt) || '暂无更新',
|
|
3339
|
-
className: historyClassName,
|
|
3340
|
-
active: state.active === session.name,
|
|
3341
|
-
sessionName: session.name,
|
|
3342
|
-
overlayNode: createTreeNodeMenu([
|
|
3343
|
-
{
|
|
3344
|
-
label: '删除 AGENT',
|
|
3345
|
-
danger: true,
|
|
3346
|
-
onClick: function () {
|
|
3347
|
-
removeAgentSessionByName(session.name, agentTitle);
|
|
3348
|
-
}
|
|
3349
|
-
}
|
|
3350
|
-
])
|
|
3351
|
-
};
|
|
3352
|
-
})
|
|
3353
|
-
};
|
|
3354
|
-
})
|
|
3355
|
-
};
|
|
3356
|
-
});
|
|
3150
|
+
function navigateTo(level, params) {
|
|
3151
|
+
const opts = params && typeof params === 'object' ? params : {};
|
|
3152
|
+
if (level === 'agents') {
|
|
3153
|
+
const containerName = String(opts.container || '').trim();
|
|
3154
|
+
if (!containerName) {
|
|
3155
|
+
return;
|
|
3156
|
+
}
|
|
3157
|
+
state.navLevel = 'agents';
|
|
3158
|
+
state.navContainer = containerName;
|
|
3159
|
+
} else {
|
|
3160
|
+
state.navLevel = 'containers';
|
|
3161
|
+
state.navContainer = '';
|
|
3162
|
+
}
|
|
3163
|
+
persistSidebarNavState();
|
|
3164
|
+
renderSessions();
|
|
3357
3165
|
}
|
|
3358
3166
|
|
|
3359
|
-
function
|
|
3360
|
-
|
|
3361
|
-
|
|
3362
|
-
|
|
3363
|
-
|
|
3364
|
-
|
|
3365
|
-
|
|
3366
|
-
|
|
3367
|
-
|
|
3368
|
-
|
|
3369
|
-
|
|
3370
|
-
|
|
3371
|
-
|
|
3372
|
-
|
|
3373
|
-
|
|
3374
|
-
|
|
3375
|
-
isLastSibling: isLastSibling,
|
|
3376
|
-
overlayNode: node.overlayNode
|
|
3167
|
+
function renderBreadcrumb() {
|
|
3168
|
+
if (!sessionBreadcrumb) {
|
|
3169
|
+
return;
|
|
3170
|
+
}
|
|
3171
|
+
sessionBreadcrumb.innerHTML = '';
|
|
3172
|
+
|
|
3173
|
+
const rootCrumb = document.createElement('button');
|
|
3174
|
+
rootCrumb.type = 'button';
|
|
3175
|
+
rootCrumb.className = 'crumb-item';
|
|
3176
|
+
rootCrumb.textContent = '全部容器';
|
|
3177
|
+
if (state.navLevel === 'containers') {
|
|
3178
|
+
rootCrumb.classList.add('is-current');
|
|
3179
|
+
rootCrumb.disabled = true;
|
|
3180
|
+
} else {
|
|
3181
|
+
rootCrumb.addEventListener('click', function () {
|
|
3182
|
+
navigateTo('containers');
|
|
3377
3183
|
});
|
|
3184
|
+
}
|
|
3185
|
+
sessionBreadcrumb.appendChild(rootCrumb);
|
|
3186
|
+
|
|
3187
|
+
if (state.navLevel === 'agents' && state.navContainer) {
|
|
3188
|
+
const sep = document.createElement('span');
|
|
3189
|
+
sep.className = 'crumb-sep';
|
|
3190
|
+
sep.setAttribute('aria-hidden', 'true');
|
|
3191
|
+
sep.textContent = '›';
|
|
3192
|
+
sessionBreadcrumb.appendChild(sep);
|
|
3193
|
+
|
|
3194
|
+
const containerCrumb = document.createElement('span');
|
|
3195
|
+
containerCrumb.className = 'crumb-item is-current';
|
|
3196
|
+
containerCrumb.textContent = state.navContainer;
|
|
3197
|
+
containerCrumb.title = state.navContainer;
|
|
3198
|
+
sessionBreadcrumb.appendChild(containerCrumb);
|
|
3199
|
+
}
|
|
3378
3200
|
|
|
3379
|
-
|
|
3380
|
-
|
|
3381
|
-
|
|
3382
|
-
|
|
3383
|
-
|
|
3201
|
+
const activeSession = getActiveSession();
|
|
3202
|
+
if (activeSession) {
|
|
3203
|
+
const activeContainerName = getSessionContainerName(activeSession);
|
|
3204
|
+
const isActiveVisible = state.navLevel === 'agents' && state.navContainer === activeContainerName;
|
|
3205
|
+
if (!isActiveVisible) {
|
|
3206
|
+
const jumpBtn = document.createElement('button');
|
|
3207
|
+
jumpBtn.type = 'button';
|
|
3208
|
+
jumpBtn.className = 'crumb-jump-active';
|
|
3209
|
+
jumpBtn.textContent = '回到当前会话';
|
|
3210
|
+
jumpBtn.addEventListener('click', function () {
|
|
3211
|
+
navigateTo('agents', { container: activeContainerName });
|
|
3384
3212
|
});
|
|
3385
|
-
|
|
3386
|
-
return;
|
|
3213
|
+
sessionBreadcrumb.appendChild(jumpBtn);
|
|
3387
3214
|
}
|
|
3215
|
+
}
|
|
3216
|
+
}
|
|
3217
|
+
|
|
3218
|
+
function renderContainerLevel(groups) {
|
|
3219
|
+
sessionList.innerHTML = '';
|
|
3220
|
+
if (!groups.length) {
|
|
3221
|
+
const empty = document.createElement('div');
|
|
3222
|
+
empty.className = 'empty';
|
|
3223
|
+
empty.textContent = state.containerSearchQuery.trim() ? '没有匹配的容器' : '暂无 manyoyo 容器';
|
|
3224
|
+
sessionList.appendChild(empty);
|
|
3225
|
+
return;
|
|
3226
|
+
}
|
|
3227
|
+
groups.forEach(function (containerGroup) {
|
|
3228
|
+
const status = sessionStatusInfo(containerGroup.status);
|
|
3229
|
+
const item = createSessionCard({
|
|
3230
|
+
kind: 'container',
|
|
3231
|
+
className: status.tone === 'history' ? 'tree-node-tone-history' : '',
|
|
3232
|
+
title: containerGroup.containerRemark || containerGroup.containerName,
|
|
3233
|
+
meta: containerGroup.containerRemark ? containerGroup.containerName : '',
|
|
3234
|
+
statusLabel: status.label,
|
|
3235
|
+
statusTone: status.tone,
|
|
3236
|
+
subMeta: containerGroup.hostPath,
|
|
3237
|
+
hasActive: containerContainsActiveSession(containerGroup),
|
|
3238
|
+
onClick: function () {
|
|
3239
|
+
navigateTo('agents', { container: containerGroup.containerName });
|
|
3240
|
+
},
|
|
3241
|
+
overlayNode: createTreeNodeMenu([
|
|
3242
|
+
{
|
|
3243
|
+
label: '新建 AGENT',
|
|
3244
|
+
onClick: function () {
|
|
3245
|
+
createAgentSession(containerGroup.containerName);
|
|
3246
|
+
}
|
|
3247
|
+
},
|
|
3248
|
+
{
|
|
3249
|
+
label: '编辑备注',
|
|
3250
|
+
onClick: function () {
|
|
3251
|
+
editContainerRemark(containerGroup);
|
|
3252
|
+
}
|
|
3253
|
+
},
|
|
3254
|
+
{
|
|
3255
|
+
label: '创建相同配置容器',
|
|
3256
|
+
onClick: function () {
|
|
3257
|
+
openCloneModal('clone-config', containerGroup.containerName);
|
|
3258
|
+
}
|
|
3259
|
+
},
|
|
3260
|
+
{
|
|
3261
|
+
label: '复制容器',
|
|
3262
|
+
onClick: function () {
|
|
3263
|
+
openCloneModal('duplicate', containerGroup.containerName);
|
|
3264
|
+
}
|
|
3265
|
+
},
|
|
3266
|
+
{
|
|
3267
|
+
label: '删除容器',
|
|
3268
|
+
danger: true,
|
|
3269
|
+
onClick: function () {
|
|
3270
|
+
removeContainerByName(containerGroup.containerName);
|
|
3271
|
+
}
|
|
3272
|
+
}
|
|
3273
|
+
])
|
|
3274
|
+
});
|
|
3275
|
+
sessionList.appendChild(item.root);
|
|
3276
|
+
});
|
|
3277
|
+
}
|
|
3388
3278
|
|
|
3389
|
-
|
|
3390
|
-
|
|
3391
|
-
|
|
3392
|
-
|
|
3279
|
+
async function editContainerRemark(containerGroup) {
|
|
3280
|
+
const current = containerGroup.containerRemark || '';
|
|
3281
|
+
const input = window.prompt('设置容器备注(留空清除备注)', current);
|
|
3282
|
+
if (input === null) {
|
|
3283
|
+
return;
|
|
3284
|
+
}
|
|
3285
|
+
const remark = String(input || '').trim();
|
|
3286
|
+
try {
|
|
3287
|
+
await api('/api/sessions/' + encodeURIComponent(containerGroup.containerName) + '/container-remark', {
|
|
3288
|
+
method: 'POST',
|
|
3289
|
+
body: JSON.stringify({ remark })
|
|
3290
|
+
});
|
|
3291
|
+
await refreshSessionsSilent({ preferredName: state.active });
|
|
3292
|
+
} catch (e) {
|
|
3293
|
+
alert(e.message);
|
|
3294
|
+
}
|
|
3295
|
+
}
|
|
3393
3296
|
|
|
3394
|
-
|
|
3395
|
-
|
|
3396
|
-
|
|
3397
|
-
|
|
3398
|
-
|
|
3399
|
-
|
|
3400
|
-
|
|
3297
|
+
async function editAgentRemark(session) {
|
|
3298
|
+
const current = session.agentRemark || '';
|
|
3299
|
+
const input = window.prompt('设置 AGENT 备注(留空清除备注)', current);
|
|
3300
|
+
if (input === null) {
|
|
3301
|
+
return;
|
|
3302
|
+
}
|
|
3303
|
+
const remark = String(input || '').trim();
|
|
3304
|
+
try {
|
|
3305
|
+
await api('/api/sessions/' + encodeURIComponent(session.name) + '/agent-remark', {
|
|
3306
|
+
method: 'POST',
|
|
3307
|
+
body: JSON.stringify({ remark })
|
|
3308
|
+
});
|
|
3309
|
+
await refreshSessionsSilent({ preferredName: state.active });
|
|
3310
|
+
} catch (e) {
|
|
3311
|
+
alert(e.message);
|
|
3312
|
+
}
|
|
3313
|
+
}
|
|
3401
3314
|
|
|
3402
|
-
|
|
3403
|
-
|
|
3404
|
-
|
|
3405
|
-
|
|
3406
|
-
|
|
3407
|
-
|
|
3408
|
-
|
|
3409
|
-
|
|
3410
|
-
|
|
3411
|
-
|
|
3412
|
-
|
|
3413
|
-
|
|
3315
|
+
function renderAgentLevel(containerGroup) {
|
|
3316
|
+
sessionList.innerHTML = '';
|
|
3317
|
+
const sessions = containerGroup && Array.isArray(containerGroup.sessions) ? containerGroup.sessions : [];
|
|
3318
|
+
if (!sessions.length) {
|
|
3319
|
+
const empty = document.createElement('div');
|
|
3320
|
+
empty.className = 'empty';
|
|
3321
|
+
empty.textContent = '该容器下暂无 AGENT';
|
|
3322
|
+
sessionList.appendChild(empty);
|
|
3323
|
+
return;
|
|
3324
|
+
}
|
|
3325
|
+
sessions.forEach(function (session) {
|
|
3326
|
+
const agentTitle = session.agentName || session.name;
|
|
3327
|
+
const displayTitle = session.agentRemark || agentTitle;
|
|
3328
|
+
const dateLabel = formatDateTime(session.updatedAt) || '暂无更新';
|
|
3329
|
+
const item = createSessionCard({
|
|
3330
|
+
kind: 'agent',
|
|
3331
|
+
title: displayTitle,
|
|
3332
|
+
meta: session.agentRemark ? agentTitle : dateLabel,
|
|
3333
|
+
subMeta: session.agentRemark ? dateLabel : '',
|
|
3334
|
+
active: state.active === session.name,
|
|
3335
|
+
onClick: function () {
|
|
3336
|
+
handleSessionItemClick(session.name);
|
|
3337
|
+
},
|
|
3338
|
+
overlayNode: createTreeNodeMenu([
|
|
3339
|
+
{
|
|
3340
|
+
label: '编辑备注',
|
|
3341
|
+
onClick: function () {
|
|
3342
|
+
editAgentRemark(session);
|
|
3343
|
+
}
|
|
3344
|
+
},
|
|
3345
|
+
{
|
|
3346
|
+
label: '删除 AGENT',
|
|
3347
|
+
danger: true,
|
|
3348
|
+
onClick: function () {
|
|
3349
|
+
removeAgentSessionByName(session.name, agentTitle);
|
|
3350
|
+
}
|
|
3351
|
+
}
|
|
3352
|
+
])
|
|
3353
|
+
});
|
|
3354
|
+
item.button.dataset.sessionName = session.name;
|
|
3355
|
+
state.sessionNodeMap.set(session.name, item.button);
|
|
3356
|
+
sessionList.appendChild(item.root);
|
|
3414
3357
|
});
|
|
3415
3358
|
}
|
|
3416
3359
|
|
|
@@ -3429,45 +3372,24 @@
|
|
|
3429
3372
|
|
|
3430
3373
|
function updateSidebarActiveSelection() {
|
|
3431
3374
|
state.sessionNodeMap.forEach(function (buttonNode, sessionName) {
|
|
3432
|
-
const isActive = sessionName === state.active;
|
|
3433
3375
|
if (!buttonNode) {
|
|
3434
3376
|
return;
|
|
3435
3377
|
}
|
|
3378
|
+
const isActive = sessionName === state.active;
|
|
3436
3379
|
buttonNode.classList.toggle('active', isActive);
|
|
3437
3380
|
buttonNode.setAttribute('aria-selected', isActive ? 'true' : 'false');
|
|
3438
3381
|
});
|
|
3439
|
-
|
|
3440
|
-
if (!sessionList) {
|
|
3441
|
-
return;
|
|
3442
|
-
}
|
|
3443
|
-
|
|
3444
|
-
sessionList.querySelectorAll('.tree-node-block.has-active').forEach(function (blockNode) {
|
|
3445
|
-
blockNode.classList.remove('has-active');
|
|
3446
|
-
});
|
|
3447
|
-
|
|
3448
|
-
const activeNode = state.sessionNodeMap.get(state.active);
|
|
3449
|
-
let cursor = activeNode ? activeNode.parentElement : null;
|
|
3450
|
-
while (cursor && cursor !== sessionList) {
|
|
3451
|
-
if (cursor.classList && cursor.classList.contains('tree-node-children')) {
|
|
3452
|
-
const blockNode = cursor.parentElement;
|
|
3453
|
-
if (blockNode && blockNode.classList && blockNode.classList.contains('tree-node-block')) {
|
|
3454
|
-
blockNode.classList.add('has-active');
|
|
3455
|
-
}
|
|
3456
|
-
}
|
|
3457
|
-
cursor = cursor.parentElement;
|
|
3458
|
-
}
|
|
3382
|
+
renderBreadcrumb();
|
|
3459
3383
|
}
|
|
3460
3384
|
|
|
3461
3385
|
function renderSessions() {
|
|
3462
|
-
const directoryCount = new Set(state.sessions.map(function (session) {
|
|
3463
|
-
return String(session && session.hostPath ? session.hostPath : '').trim() || '未配置目录';
|
|
3464
|
-
}).filter(Boolean)).size;
|
|
3465
3386
|
const containerCount = new Set(state.sessions.map(function (session) {
|
|
3466
3387
|
return session && session.containerName ? session.containerName : '';
|
|
3467
3388
|
}).filter(Boolean)).size;
|
|
3468
3389
|
sessionCount.textContent = state.loadingSessions
|
|
3469
3390
|
? '加载中...'
|
|
3470
|
-
: `${
|
|
3391
|
+
: `${containerCount} 个容器 / ${state.sessions.length} 个 AGENT`;
|
|
3392
|
+
renderBreadcrumb();
|
|
3471
3393
|
|
|
3472
3394
|
if (state.loadingSessions) {
|
|
3473
3395
|
renderSessionsLoading();
|
|
@@ -3485,18 +3407,153 @@
|
|
|
3485
3407
|
return;
|
|
3486
3408
|
}
|
|
3487
3409
|
|
|
3488
|
-
sessionList.innerHTML = '';
|
|
3489
3410
|
state.sessionNodeMap.clear();
|
|
3490
|
-
state.sessionRenderMode = '
|
|
3411
|
+
state.sessionRenderMode = 'cards';
|
|
3491
3412
|
|
|
3492
|
-
const
|
|
3493
|
-
|
|
3494
|
-
|
|
3495
|
-
|
|
3413
|
+
const groups = groupSessionsByContainer(state.sessions);
|
|
3414
|
+
let activeGroup = null;
|
|
3415
|
+
if (state.navLevel === 'agents') {
|
|
3416
|
+
activeGroup = groups.find(function (group) {
|
|
3417
|
+
return group.containerName === state.navContainer;
|
|
3418
|
+
}) || null;
|
|
3419
|
+
if (!activeGroup) {
|
|
3420
|
+
state.navLevel = 'containers';
|
|
3421
|
+
state.navContainer = '';
|
|
3422
|
+
persistSidebarNavState();
|
|
3423
|
+
renderBreadcrumb();
|
|
3424
|
+
}
|
|
3425
|
+
}
|
|
3496
3426
|
|
|
3427
|
+
if (containerSearchInput) {
|
|
3428
|
+
containerSearchInput.hidden = state.navLevel !== 'containers';
|
|
3429
|
+
}
|
|
3430
|
+
|
|
3431
|
+
if (state.navLevel === 'agents' && activeGroup) {
|
|
3432
|
+
renderAgentLevel(activeGroup);
|
|
3433
|
+
} else {
|
|
3434
|
+
renderContainerLevel(filterContainerGroupsByQuery(groups, state.containerSearchQuery));
|
|
3435
|
+
}
|
|
3436
|
+
|
|
3437
|
+
updateSidebarActiveSelection();
|
|
3497
3438
|
scrollActiveSessionIntoView();
|
|
3498
3439
|
}
|
|
3499
3440
|
|
|
3441
|
+
function filterContainerGroupsByQuery(groups, query) {
|
|
3442
|
+
const text = String(query || '').trim().toLowerCase();
|
|
3443
|
+
if (!text) {
|
|
3444
|
+
return groups;
|
|
3445
|
+
}
|
|
3446
|
+
return groups.filter(function (group) {
|
|
3447
|
+
return group.containerName.toLowerCase().includes(text)
|
|
3448
|
+
|| String(group.hostPath || '').toLowerCase().includes(text);
|
|
3449
|
+
});
|
|
3450
|
+
}
|
|
3451
|
+
|
|
3452
|
+
function suggestCloneContainerName(baseName) {
|
|
3453
|
+
const taken = new Set(state.sessions.map(getSessionContainerName).filter(Boolean));
|
|
3454
|
+
const escapedBase = baseName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
3455
|
+
const pattern = new RegExp(`^${escapedBase}-copy(\\d+)$`);
|
|
3456
|
+
let maxN = 0;
|
|
3457
|
+
taken.forEach(function (name) {
|
|
3458
|
+
const matched = name.match(pattern);
|
|
3459
|
+
if (matched) {
|
|
3460
|
+
const n = Number(matched[1]);
|
|
3461
|
+
if (Number.isFinite(n) && n > maxN) {
|
|
3462
|
+
maxN = n;
|
|
3463
|
+
}
|
|
3464
|
+
}
|
|
3465
|
+
});
|
|
3466
|
+
return `${baseName}-copy${maxN + 1}`;
|
|
3467
|
+
}
|
|
3468
|
+
|
|
3469
|
+
function openCloneModal(mode, sourceContainerName) {
|
|
3470
|
+
const source = String(sourceContainerName || '').trim();
|
|
3471
|
+
if (!source) {
|
|
3472
|
+
return;
|
|
3473
|
+
}
|
|
3474
|
+
state.cloneModal.open = true;
|
|
3475
|
+
state.cloneModal.mode = mode;
|
|
3476
|
+
state.cloneModal.sourceContainerName = source;
|
|
3477
|
+
state.cloneModal.nameDraft = suggestCloneContainerName(source);
|
|
3478
|
+
state.cloneModal.submitting = false;
|
|
3479
|
+
state.cloneModal.error = '';
|
|
3480
|
+
renderCloneModal();
|
|
3481
|
+
syncUi();
|
|
3482
|
+
}
|
|
3483
|
+
|
|
3484
|
+
function closeCloneModal() {
|
|
3485
|
+
state.cloneModal.open = false;
|
|
3486
|
+
state.cloneModal.mode = '';
|
|
3487
|
+
state.cloneModal.sourceContainerName = '';
|
|
3488
|
+
state.cloneModal.nameDraft = '';
|
|
3489
|
+
state.cloneModal.submitting = false;
|
|
3490
|
+
state.cloneModal.error = '';
|
|
3491
|
+
renderCloneModal();
|
|
3492
|
+
syncUi();
|
|
3493
|
+
}
|
|
3494
|
+
|
|
3495
|
+
function showCloneModalError(message) {
|
|
3496
|
+
if (!cloneNameError) {
|
|
3497
|
+
return;
|
|
3498
|
+
}
|
|
3499
|
+
const text = String(message || '').trim();
|
|
3500
|
+
cloneNameError.hidden = !text;
|
|
3501
|
+
cloneNameError.textContent = text;
|
|
3502
|
+
}
|
|
3503
|
+
|
|
3504
|
+
function renderCloneModal() {
|
|
3505
|
+
if (!cloneNameModal) {
|
|
3506
|
+
return;
|
|
3507
|
+
}
|
|
3508
|
+
setModalVisible(cloneNameModal, state.cloneModal.open);
|
|
3509
|
+
if (cloneNameTitle) {
|
|
3510
|
+
cloneNameTitle.textContent = state.cloneModal.mode === 'duplicate' ? '复制容器' : '创建相同配置容器';
|
|
3511
|
+
}
|
|
3512
|
+
if (cloneNameTip) {
|
|
3513
|
+
cloneNameTip.textContent = state.cloneModal.mode === 'duplicate'
|
|
3514
|
+
? `将复制「${state.cloneModal.sourceContainerName}」的配置与全部 AGENT 对话历史到新容器。`
|
|
3515
|
+
: `将以「${state.cloneModal.sourceContainerName}」的配置创建一个新容器,不复制对话历史。`;
|
|
3516
|
+
}
|
|
3517
|
+
if (cloneNameInput && document.activeElement !== cloneNameInput) {
|
|
3518
|
+
cloneNameInput.value = state.cloneModal.nameDraft;
|
|
3519
|
+
}
|
|
3520
|
+
if (cloneNameConfirmBtn) {
|
|
3521
|
+
cloneNameConfirmBtn.disabled = state.cloneModal.submitting;
|
|
3522
|
+
}
|
|
3523
|
+
if (cloneNameCancelBtn) {
|
|
3524
|
+
cloneNameCancelBtn.disabled = state.cloneModal.submitting;
|
|
3525
|
+
}
|
|
3526
|
+
showCloneModalError(state.cloneModal.error);
|
|
3527
|
+
}
|
|
3528
|
+
|
|
3529
|
+
async function submitCloneModal() {
|
|
3530
|
+
if (state.cloneModal.submitting) {
|
|
3531
|
+
return;
|
|
3532
|
+
}
|
|
3533
|
+
const name = String(state.cloneModal.nameDraft || '').trim();
|
|
3534
|
+
const mode = state.cloneModal.mode;
|
|
3535
|
+
const source = state.cloneModal.sourceContainerName;
|
|
3536
|
+
state.cloneModal.submitting = true;
|
|
3537
|
+
showCloneModalError('');
|
|
3538
|
+
renderCloneModal();
|
|
3539
|
+
try {
|
|
3540
|
+
const endpoint = mode === 'duplicate' ? 'duplicate' : 'clone-config';
|
|
3541
|
+
const data = await api('/api/sessions/' + encodeURIComponent(source) + '/' + endpoint, {
|
|
3542
|
+
method: 'POST',
|
|
3543
|
+
body: JSON.stringify(name ? { containerName: name } : {})
|
|
3544
|
+
});
|
|
3545
|
+
closeCloneModal();
|
|
3546
|
+
await loadSessions(data.name);
|
|
3547
|
+
if (data.resumeMayFail) {
|
|
3548
|
+
alert('复制完成。注意:AGENT 对话历史已复制,但底层 CLI 会话续接可能失败,如遇失败请以新对话继续。');
|
|
3549
|
+
}
|
|
3550
|
+
} catch (e) {
|
|
3551
|
+
state.cloneModal.submitting = false;
|
|
3552
|
+
showCloneModalError(e.message);
|
|
3553
|
+
renderCloneModal();
|
|
3554
|
+
}
|
|
3555
|
+
}
|
|
3556
|
+
|
|
3500
3557
|
function renderMessagesLoading() {
|
|
3501
3558
|
messagesNode.innerHTML = '';
|
|
3502
3559
|
state.messageRenderKeys = [];
|
|
@@ -3725,7 +3782,7 @@
|
|
|
3725
3782
|
function applySessionsSnapshot(rawSessions, preferredName, preferredContainerName) {
|
|
3726
3783
|
const previousActive = state.active;
|
|
3727
3784
|
state.sessions = Array.isArray(rawSessions) ? rawSessions : [];
|
|
3728
|
-
|
|
3785
|
+
pruneSidebarNavState();
|
|
3729
3786
|
|
|
3730
3787
|
if (typeof preferredName === 'string' && preferredName.trim()) {
|
|
3731
3788
|
state.active = preferredName.trim();
|
|
@@ -3740,7 +3797,7 @@
|
|
|
3740
3797
|
state.active = findLatestCreatedSessionName(state.sessions, preferredContainerName) || state.sessions[0].name;
|
|
3741
3798
|
}
|
|
3742
3799
|
if (state.active && state.active !== previousActive) {
|
|
3743
|
-
|
|
3800
|
+
ensureSessionNavPath(state.active);
|
|
3744
3801
|
state.pendingActiveSessionScroll = true;
|
|
3745
3802
|
}
|
|
3746
3803
|
if (state.terminal.sessionName && state.terminal.sessionName !== state.active) {
|
|
@@ -4506,7 +4563,9 @@
|
|
|
4506
4563
|
renderMessages(state.messages, { forceFullRender: true });
|
|
4507
4564
|
closeComposerOptionsMenu();
|
|
4508
4565
|
syncUi();
|
|
4509
|
-
|
|
4566
|
+
if (!isMobileLayout()) {
|
|
4567
|
+
commandInput.focus();
|
|
4568
|
+
}
|
|
4510
4569
|
});
|
|
4511
4570
|
}
|
|
4512
4571
|
|
|
@@ -4516,7 +4575,9 @@
|
|
|
4516
4575
|
renderMessages(state.messages, { forceFullRender: true });
|
|
4517
4576
|
closeComposerOptionsMenu();
|
|
4518
4577
|
syncUi();
|
|
4519
|
-
|
|
4578
|
+
if (!isMobileLayout()) {
|
|
4579
|
+
commandInput.focus();
|
|
4580
|
+
}
|
|
4520
4581
|
});
|
|
4521
4582
|
}
|
|
4522
4583
|
|
|
@@ -4530,7 +4591,9 @@
|
|
|
4530
4591
|
viewActivityBtn.addEventListener('click', function () {
|
|
4531
4592
|
closeWorkspaceSwitcherMenu();
|
|
4532
4593
|
setActiveTab('activity');
|
|
4533
|
-
|
|
4594
|
+
if (!isMobileLayout()) {
|
|
4595
|
+
commandInput.focus();
|
|
4596
|
+
}
|
|
4534
4597
|
});
|
|
4535
4598
|
}
|
|
4536
4599
|
|
|
@@ -4672,6 +4735,37 @@
|
|
|
4672
4735
|
});
|
|
4673
4736
|
}
|
|
4674
4737
|
|
|
4738
|
+
if (cloneNameModal) {
|
|
4739
|
+
cloneNameModal.addEventListener('click', function (event) {
|
|
4740
|
+
if (event.target === cloneNameModal && !state.cloneModal.submitting) {
|
|
4741
|
+
closeCloneModal();
|
|
4742
|
+
}
|
|
4743
|
+
});
|
|
4744
|
+
}
|
|
4745
|
+
if (cloneNameCancelBtn) {
|
|
4746
|
+
cloneNameCancelBtn.addEventListener('click', function () {
|
|
4747
|
+
if (!state.cloneModal.submitting) {
|
|
4748
|
+
closeCloneModal();
|
|
4749
|
+
}
|
|
4750
|
+
});
|
|
4751
|
+
}
|
|
4752
|
+
if (cloneNameConfirmBtn) {
|
|
4753
|
+
cloneNameConfirmBtn.addEventListener('click', function () {
|
|
4754
|
+
submitCloneModal();
|
|
4755
|
+
});
|
|
4756
|
+
}
|
|
4757
|
+
if (cloneNameInput) {
|
|
4758
|
+
cloneNameInput.addEventListener('input', function () {
|
|
4759
|
+
state.cloneModal.nameDraft = cloneNameInput.value;
|
|
4760
|
+
});
|
|
4761
|
+
}
|
|
4762
|
+
if (containerSearchInput) {
|
|
4763
|
+
containerSearchInput.addEventListener('input', function () {
|
|
4764
|
+
state.containerSearchQuery = containerSearchInput.value;
|
|
4765
|
+
renderSessions();
|
|
4766
|
+
});
|
|
4767
|
+
}
|
|
4768
|
+
|
|
4675
4769
|
window.addEventListener('keydown', function (event) {
|
|
4676
4770
|
if (event.key === 'Escape' && state.configModalOpen) {
|
|
4677
4771
|
closeConfigModal();
|
|
@@ -4692,6 +4786,9 @@
|
|
|
4692
4786
|
closeExternalLinkModalView();
|
|
4693
4787
|
syncUi();
|
|
4694
4788
|
}
|
|
4789
|
+
if (event.key === 'Escape' && state.cloneModal.open && !state.cloneModal.submitting) {
|
|
4790
|
+
closeCloneModal();
|
|
4791
|
+
}
|
|
4695
4792
|
if (event.key === 'Escape' && state.mobileSidebarOpen) {
|
|
4696
4793
|
closeMobileSessionPanel();
|
|
4697
4794
|
}
|
|
@@ -4802,7 +4899,7 @@
|
|
|
4802
4899
|
disconnectTerminal('', true);
|
|
4803
4900
|
});
|
|
4804
4901
|
|
|
4805
|
-
|
|
4902
|
+
loadSidebarNavState();
|
|
4806
4903
|
renderSessions();
|
|
4807
4904
|
renderMessages(state.messages);
|
|
4808
4905
|
setMobileSessionPanel(false);
|