@xcanwin/manyoyo 6.2.5 → 6.2.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/web/frontend/app.css +111 -137
- package/lib/web/frontend/app.html +28 -1
- package/lib/web/frontend/app.js +482 -396
- package/lib/web/frontend/file-browser.js +77 -1
- package/lib/web/frontend/markdown-renderer.js +16 -4
- package/lib/web/frontend/markdown.css +21 -21
- package/lib/web/server.js +452 -22
- 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;
|
|
@@ -2428,7 +2363,7 @@
|
|
|
2428
2363
|
}
|
|
2429
2364
|
document.body.classList.toggle(
|
|
2430
2365
|
'modal-open',
|
|
2431
|
-
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
|
|
2432
2367
|
);
|
|
2433
2368
|
if (composer) {
|
|
2434
2369
|
composer.hidden = !activityTab;
|
|
@@ -2844,7 +2779,7 @@
|
|
|
2844
2779
|
disconnectTerminal('会话切换,终端已断开', true);
|
|
2845
2780
|
}
|
|
2846
2781
|
state.active = sessionName;
|
|
2847
|
-
|
|
2782
|
+
ensureSessionNavPath(sessionName);
|
|
2848
2783
|
state.sessionDetail = null;
|
|
2849
2784
|
state.sessionDetailError = '';
|
|
2850
2785
|
if (isMobileLayout()) {
|
|
@@ -2899,48 +2834,33 @@
|
|
|
2899
2834
|
}
|
|
2900
2835
|
}
|
|
2901
2836
|
|
|
2902
|
-
function
|
|
2837
|
+
function groupSessionsByContainer(sessions) {
|
|
2903
2838
|
const groups = new Map();
|
|
2904
2839
|
(Array.isArray(sessions) ? sessions : []).forEach(function (session) {
|
|
2905
|
-
const
|
|
2906
|
-
if (!
|
|
2907
|
-
|
|
2908
|
-
path: directoryPath,
|
|
2909
|
-
updatedAt: session && session.updatedAt ? session.updatedAt : '',
|
|
2910
|
-
containers: new Map()
|
|
2911
|
-
});
|
|
2912
|
-
}
|
|
2913
|
-
const directoryGroup = groups.get(directoryPath);
|
|
2914
|
-
if (session && session.updatedAt && (!directoryGroup.updatedAt || new Date(session.updatedAt).getTime() > new Date(directoryGroup.updatedAt).getTime())) {
|
|
2915
|
-
directoryGroup.updatedAt = session.updatedAt;
|
|
2840
|
+
const containerName = getSessionContainerName(session);
|
|
2841
|
+
if (!containerName) {
|
|
2842
|
+
return;
|
|
2916
2843
|
}
|
|
2917
|
-
|
|
2918
|
-
|
|
2919
|
-
if (!directoryGroup.containers.has(containerName)) {
|
|
2920
|
-
directoryGroup.containers.set(containerName, {
|
|
2844
|
+
if (!groups.has(containerName)) {
|
|
2845
|
+
groups.set(containerName, {
|
|
2921
2846
|
containerName: containerName,
|
|
2847
|
+
containerRemark: session && session.containerRemark ? session.containerRemark : '',
|
|
2922
2848
|
status: session && session.status ? session.status : 'history',
|
|
2923
2849
|
image: session && session.image ? session.image : '',
|
|
2850
|
+
hostPath: getSessionDirectoryPath(session),
|
|
2924
2851
|
updatedAt: session && session.updatedAt ? session.updatedAt : '',
|
|
2925
2852
|
sessions: []
|
|
2926
2853
|
});
|
|
2927
2854
|
}
|
|
2928
|
-
const containerGroup =
|
|
2855
|
+
const containerGroup = groups.get(containerName);
|
|
2929
2856
|
containerGroup.sessions.push(session);
|
|
2930
2857
|
if (session && session.updatedAt && (!containerGroup.updatedAt || new Date(session.updatedAt).getTime() > new Date(containerGroup.updatedAt).getTime())) {
|
|
2931
2858
|
containerGroup.updatedAt = session.updatedAt;
|
|
2932
2859
|
}
|
|
2933
2860
|
});
|
|
2934
2861
|
return Array.from(groups.values()).map(function (group) {
|
|
2935
|
-
group.
|
|
2936
|
-
|
|
2937
|
-
const timeB = b.updatedAt ? new Date(b.updatedAt).getTime() : 0;
|
|
2938
|
-
return timeB - timeA;
|
|
2939
|
-
});
|
|
2940
|
-
group.containers.forEach(function (containerGroup) {
|
|
2941
|
-
containerGroup.sessions.sort(function (a, b) {
|
|
2942
|
-
return compareSessionByCreatedDesc(a, b);
|
|
2943
|
-
});
|
|
2862
|
+
group.sessions.sort(function (a, b) {
|
|
2863
|
+
return compareSessionByCreatedDesc(a, b);
|
|
2944
2864
|
});
|
|
2945
2865
|
return group;
|
|
2946
2866
|
}).sort(function (a, b) {
|
|
@@ -3168,254 +3088,272 @@
|
|
|
3168
3088
|
}
|
|
3169
3089
|
}
|
|
3170
3090
|
|
|
3171
|
-
function
|
|
3172
|
-
const button = document.createElement('button');
|
|
3173
|
-
button.type = 'button';
|
|
3174
|
-
button.className = 'disclosure-toggle';
|
|
3175
|
-
button.dataset.disclosureLabel = label || '';
|
|
3176
|
-
button.setAttribute('aria-expanded', expanded ? 'true' : 'false');
|
|
3177
|
-
button.setAttribute('aria-label', `${expanded ? '折叠' : '展开'}${label ? ` ${label}` : ''}`);
|
|
3178
|
-
button.innerHTML = '<svg viewBox="0 0 12 12" aria-hidden="true" focusable="false"><path d="M4 2.5L8 6L4 9.5"></path></svg>';
|
|
3179
|
-
return button;
|
|
3180
|
-
}
|
|
3181
|
-
|
|
3182
|
-
function createTreePrefixSegment() {
|
|
3183
|
-
const segment = document.createElement('span');
|
|
3184
|
-
segment.className = 'tree-prefix-segment';
|
|
3185
|
-
segment.setAttribute('aria-hidden', 'true');
|
|
3186
|
-
return segment;
|
|
3187
|
-
}
|
|
3188
|
-
|
|
3189
|
-
function createTreePrefix(ancestorHasNext, isLastSibling, options) {
|
|
3190
|
-
const opts = options && typeof options === 'object' ? options : {};
|
|
3191
|
-
const prefix = document.createElement('div');
|
|
3192
|
-
prefix.className = 'tree-prefix';
|
|
3193
|
-
|
|
3194
|
-
(Array.isArray(ancestorHasNext) ? ancestorHasNext : []).forEach(function () {
|
|
3195
|
-
prefix.appendChild(createTreePrefixSegment());
|
|
3196
|
-
});
|
|
3197
|
-
|
|
3198
|
-
const branch = document.createElement('span');
|
|
3199
|
-
branch.className = `tree-prefix-branch ${isLastSibling ? 'is-last' : 'is-mid'}`;
|
|
3200
|
-
branch.setAttribute('aria-hidden', 'true');
|
|
3201
|
-
prefix.appendChild(branch);
|
|
3202
|
-
|
|
3203
|
-
let disclosure = null;
|
|
3204
|
-
let disclosureWrap = null;
|
|
3205
|
-
if (opts.expandable) {
|
|
3206
|
-
disclosure = createDisclosureButton(!!opts.expanded, opts.label || '');
|
|
3207
|
-
disclosureWrap = document.createElement('span');
|
|
3208
|
-
disclosureWrap.className = 'tree-prefix-toggle';
|
|
3209
|
-
disclosureWrap.appendChild(disclosure);
|
|
3210
|
-
prefix.appendChild(disclosureWrap);
|
|
3211
|
-
} else {
|
|
3212
|
-
const leaf = document.createElement('span');
|
|
3213
|
-
leaf.className = 'tree-prefix-leaf';
|
|
3214
|
-
leaf.setAttribute('aria-hidden', 'true');
|
|
3215
|
-
prefix.appendChild(leaf);
|
|
3216
|
-
}
|
|
3217
|
-
|
|
3218
|
-
return { root: prefix, disclosure, disclosureWrap };
|
|
3219
|
-
}
|
|
3220
|
-
|
|
3221
|
-
function setDisclosureExpanded(control, expanded) {
|
|
3222
|
-
if (!control) {
|
|
3223
|
-
return;
|
|
3224
|
-
}
|
|
3225
|
-
const nextValue = expanded ? 'true' : 'false';
|
|
3226
|
-
if (control.button) {
|
|
3227
|
-
control.button.setAttribute('aria-expanded', nextValue);
|
|
3228
|
-
}
|
|
3229
|
-
if (control.disclosure) {
|
|
3230
|
-
control.disclosure.setAttribute('aria-expanded', nextValue);
|
|
3231
|
-
const label = control.disclosure.dataset.disclosureLabel || '';
|
|
3232
|
-
control.disclosure.setAttribute('aria-label', `${expanded ? '折叠' : '展开'}${label ? ` ${label}` : ''}`);
|
|
3233
|
-
}
|
|
3234
|
-
}
|
|
3235
|
-
|
|
3236
|
-
function createTreeItem(options) {
|
|
3091
|
+
function createSessionCard(options) {
|
|
3237
3092
|
const opts = options && typeof options === 'object' ? options : {};
|
|
3238
|
-
const
|
|
3239
|
-
|
|
3240
|
-
|
|
3241
|
-
row.setAttribute('role', 'none');
|
|
3242
|
-
|
|
3243
|
-
const prefixControl = createTreePrefix(opts.ancestorHasNext, !!opts.isLastSibling, {
|
|
3244
|
-
expandable: !!opts.expandable,
|
|
3245
|
-
expanded: !!opts.expanded,
|
|
3246
|
-
label: opts.disclosureLabel || opts.title || ''
|
|
3247
|
-
});
|
|
3248
|
-
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);
|
|
3249
3096
|
|
|
3250
3097
|
const button = document.createElement('button');
|
|
3251
3098
|
button.type = 'button';
|
|
3252
|
-
button.className =
|
|
3253
|
-
button.
|
|
3254
|
-
button.setAttribute('aria-level', String(opts.level || 1));
|
|
3255
|
-
if (opts.expandable) {
|
|
3256
|
-
button.setAttribute('aria-expanded', opts.expanded ? 'true' : 'false');
|
|
3257
|
-
}
|
|
3099
|
+
button.className = 'session-card-button';
|
|
3100
|
+
button.classList.toggle('active', !!opts.active);
|
|
3258
3101
|
if (opts.kind === 'agent') {
|
|
3259
3102
|
button.setAttribute('aria-selected', opts.active ? 'true' : 'false');
|
|
3260
3103
|
}
|
|
3261
|
-
button.
|
|
3104
|
+
button.addEventListener('click', function () {
|
|
3105
|
+
if (typeof opts.onClick === 'function') {
|
|
3106
|
+
opts.onClick();
|
|
3107
|
+
}
|
|
3108
|
+
});
|
|
3262
3109
|
|
|
3263
3110
|
const main = document.createElement('div');
|
|
3264
|
-
main.className = '
|
|
3111
|
+
main.className = 'session-card-main';
|
|
3265
3112
|
|
|
3266
|
-
const
|
|
3267
|
-
|
|
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';
|
|
3268
3117
|
title.textContent = opts.title || '';
|
|
3269
3118
|
title.title = opts.title || '';
|
|
3270
|
-
|
|
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);
|
|
3271
3127
|
|
|
3272
3128
|
if (opts.meta) {
|
|
3273
3129
|
const meta = document.createElement('div');
|
|
3274
|
-
meta.className =
|
|
3130
|
+
meta.className = 'session-card-meta';
|
|
3275
3131
|
meta.textContent = opts.meta;
|
|
3276
3132
|
main.appendChild(meta);
|
|
3277
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
|
+
}
|
|
3278
3141
|
|
|
3279
3142
|
button.appendChild(main);
|
|
3280
|
-
|
|
3143
|
+
card.appendChild(button);
|
|
3281
3144
|
if (opts.overlayNode) {
|
|
3282
|
-
|
|
3145
|
+
card.appendChild(opts.overlayNode);
|
|
3283
3146
|
}
|
|
3284
|
-
|
|
3285
|
-
const control = {
|
|
3286
|
-
root: row,
|
|
3287
|
-
button,
|
|
3288
|
-
disclosure: prefixControl.disclosure
|
|
3289
|
-
};
|
|
3290
|
-
setDisclosureExpanded(control, !!opts.expanded);
|
|
3291
|
-
return control;
|
|
3147
|
+
return { root: card, button };
|
|
3292
3148
|
}
|
|
3293
3149
|
|
|
3294
|
-
function
|
|
3295
|
-
|
|
3296
|
-
|
|
3297
|
-
|
|
3298
|
-
|
|
3299
|
-
|
|
3300
|
-
|
|
3301
|
-
|
|
3302
|
-
|
|
3303
|
-
|
|
3304
|
-
|
|
3305
|
-
|
|
3306
|
-
|
|
3307
|
-
|
|
3308
|
-
|
|
3309
|
-
const containerMenu = createTreeNodeMenu([
|
|
3310
|
-
{
|
|
3311
|
-
label: '新建 AGENT',
|
|
3312
|
-
onClick: function () {
|
|
3313
|
-
createAgentSession(containerName);
|
|
3314
|
-
}
|
|
3315
|
-
},
|
|
3316
|
-
{
|
|
3317
|
-
label: '删除容器',
|
|
3318
|
-
danger: true,
|
|
3319
|
-
onClick: function () {
|
|
3320
|
-
removeContainerByName(containerName);
|
|
3321
|
-
}
|
|
3322
|
-
}
|
|
3323
|
-
]);
|
|
3324
|
-
|
|
3325
|
-
return {
|
|
3326
|
-
kind: 'container',
|
|
3327
|
-
title: containerName,
|
|
3328
|
-
meta: status.label,
|
|
3329
|
-
metaClassName: `tree-node-status ${status.tone}`,
|
|
3330
|
-
className: historyClassName,
|
|
3331
|
-
disclosureLabel: containerName,
|
|
3332
|
-
expanded: isContainerExpanded(containerGroup),
|
|
3333
|
-
hasActive: containerContainsActiveSession(containerGroup),
|
|
3334
|
-
overlayNode: containerMenu,
|
|
3335
|
-
onToggle: function (expanded) {
|
|
3336
|
-
setSidebarContainerExpanded(containerGroup.containerName, expanded);
|
|
3337
|
-
},
|
|
3338
|
-
children: (Array.isArray(containerGroup.sessions) ? containerGroup.sessions : []).map(function (session) {
|
|
3339
|
-
const agentTitle = session.agentName || session.name;
|
|
3340
|
-
return {
|
|
3341
|
-
kind: 'agent',
|
|
3342
|
-
title: agentTitle,
|
|
3343
|
-
meta: formatDateTime(session.updatedAt) || '暂无更新',
|
|
3344
|
-
className: historyClassName,
|
|
3345
|
-
active: state.active === session.name,
|
|
3346
|
-
sessionName: session.name,
|
|
3347
|
-
overlayNode: createTreeNodeMenu([
|
|
3348
|
-
{
|
|
3349
|
-
label: '删除 AGENT',
|
|
3350
|
-
danger: true,
|
|
3351
|
-
onClick: function () {
|
|
3352
|
-
removeAgentSessionByName(session.name, agentTitle);
|
|
3353
|
-
}
|
|
3354
|
-
}
|
|
3355
|
-
])
|
|
3356
|
-
};
|
|
3357
|
-
})
|
|
3358
|
-
};
|
|
3359
|
-
})
|
|
3360
|
-
};
|
|
3361
|
-
});
|
|
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();
|
|
3362
3165
|
}
|
|
3363
3166
|
|
|
3364
|
-
function
|
|
3365
|
-
|
|
3366
|
-
|
|
3367
|
-
|
|
3368
|
-
|
|
3369
|
-
|
|
3370
|
-
|
|
3371
|
-
|
|
3372
|
-
|
|
3373
|
-
|
|
3374
|
-
|
|
3375
|
-
|
|
3376
|
-
|
|
3377
|
-
|
|
3378
|
-
|
|
3379
|
-
|
|
3380
|
-
isLastSibling: isLastSibling,
|
|
3381
|
-
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');
|
|
3382
3183
|
});
|
|
3184
|
+
}
|
|
3185
|
+
sessionBreadcrumb.appendChild(rootCrumb);
|
|
3383
3186
|
|
|
3384
|
-
|
|
3385
|
-
|
|
3386
|
-
|
|
3387
|
-
|
|
3388
|
-
|
|
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
|
+
}
|
|
3200
|
+
|
|
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 });
|
|
3389
3212
|
});
|
|
3390
|
-
|
|
3391
|
-
return;
|
|
3213
|
+
sessionBreadcrumb.appendChild(jumpBtn);
|
|
3392
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
|
+
}
|
|
3393
3278
|
|
|
3394
|
-
|
|
3395
|
-
|
|
3396
|
-
|
|
3397
|
-
|
|
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
|
+
}
|
|
3398
3296
|
|
|
3399
|
-
|
|
3400
|
-
|
|
3401
|
-
|
|
3402
|
-
|
|
3403
|
-
|
|
3404
|
-
|
|
3405
|
-
|
|
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
|
+
}
|
|
3406
3314
|
|
|
3407
|
-
|
|
3408
|
-
|
|
3409
|
-
|
|
3410
|
-
|
|
3411
|
-
|
|
3412
|
-
|
|
3413
|
-
|
|
3414
|
-
|
|
3415
|
-
|
|
3416
|
-
|
|
3417
|
-
|
|
3418
|
-
|
|
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);
|
|
3419
3357
|
});
|
|
3420
3358
|
}
|
|
3421
3359
|
|
|
@@ -3434,45 +3372,24 @@
|
|
|
3434
3372
|
|
|
3435
3373
|
function updateSidebarActiveSelection() {
|
|
3436
3374
|
state.sessionNodeMap.forEach(function (buttonNode, sessionName) {
|
|
3437
|
-
const isActive = sessionName === state.active;
|
|
3438
3375
|
if (!buttonNode) {
|
|
3439
3376
|
return;
|
|
3440
3377
|
}
|
|
3378
|
+
const isActive = sessionName === state.active;
|
|
3441
3379
|
buttonNode.classList.toggle('active', isActive);
|
|
3442
3380
|
buttonNode.setAttribute('aria-selected', isActive ? 'true' : 'false');
|
|
3443
3381
|
});
|
|
3444
|
-
|
|
3445
|
-
if (!sessionList) {
|
|
3446
|
-
return;
|
|
3447
|
-
}
|
|
3448
|
-
|
|
3449
|
-
sessionList.querySelectorAll('.tree-node-block.has-active').forEach(function (blockNode) {
|
|
3450
|
-
blockNode.classList.remove('has-active');
|
|
3451
|
-
});
|
|
3452
|
-
|
|
3453
|
-
const activeNode = state.sessionNodeMap.get(state.active);
|
|
3454
|
-
let cursor = activeNode ? activeNode.parentElement : null;
|
|
3455
|
-
while (cursor && cursor !== sessionList) {
|
|
3456
|
-
if (cursor.classList && cursor.classList.contains('tree-node-children')) {
|
|
3457
|
-
const blockNode = cursor.parentElement;
|
|
3458
|
-
if (blockNode && blockNode.classList && blockNode.classList.contains('tree-node-block')) {
|
|
3459
|
-
blockNode.classList.add('has-active');
|
|
3460
|
-
}
|
|
3461
|
-
}
|
|
3462
|
-
cursor = cursor.parentElement;
|
|
3463
|
-
}
|
|
3382
|
+
renderBreadcrumb();
|
|
3464
3383
|
}
|
|
3465
3384
|
|
|
3466
3385
|
function renderSessions() {
|
|
3467
|
-
const directoryCount = new Set(state.sessions.map(function (session) {
|
|
3468
|
-
return String(session && session.hostPath ? session.hostPath : '').trim() || '未配置目录';
|
|
3469
|
-
}).filter(Boolean)).size;
|
|
3470
3386
|
const containerCount = new Set(state.sessions.map(function (session) {
|
|
3471
3387
|
return session && session.containerName ? session.containerName : '';
|
|
3472
3388
|
}).filter(Boolean)).size;
|
|
3473
3389
|
sessionCount.textContent = state.loadingSessions
|
|
3474
3390
|
? '加载中...'
|
|
3475
|
-
: `${
|
|
3391
|
+
: `${containerCount} 个容器 / ${state.sessions.length} 个 AGENT`;
|
|
3392
|
+
renderBreadcrumb();
|
|
3476
3393
|
|
|
3477
3394
|
if (state.loadingSessions) {
|
|
3478
3395
|
renderSessionsLoading();
|
|
@@ -3490,18 +3407,153 @@
|
|
|
3490
3407
|
return;
|
|
3491
3408
|
}
|
|
3492
3409
|
|
|
3493
|
-
sessionList.innerHTML = '';
|
|
3494
3410
|
state.sessionNodeMap.clear();
|
|
3495
|
-
state.sessionRenderMode = '
|
|
3411
|
+
state.sessionRenderMode = 'cards';
|
|
3496
3412
|
|
|
3497
|
-
const
|
|
3498
|
-
|
|
3499
|
-
|
|
3500
|
-
|
|
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
|
+
}
|
|
3501
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();
|
|
3502
3438
|
scrollActiveSessionIntoView();
|
|
3503
3439
|
}
|
|
3504
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
|
+
|
|
3505
3557
|
function renderMessagesLoading() {
|
|
3506
3558
|
messagesNode.innerHTML = '';
|
|
3507
3559
|
state.messageRenderKeys = [];
|
|
@@ -3730,7 +3782,7 @@
|
|
|
3730
3782
|
function applySessionsSnapshot(rawSessions, preferredName, preferredContainerName) {
|
|
3731
3783
|
const previousActive = state.active;
|
|
3732
3784
|
state.sessions = Array.isArray(rawSessions) ? rawSessions : [];
|
|
3733
|
-
|
|
3785
|
+
pruneSidebarNavState();
|
|
3734
3786
|
|
|
3735
3787
|
if (typeof preferredName === 'string' && preferredName.trim()) {
|
|
3736
3788
|
state.active = preferredName.trim();
|
|
@@ -3745,7 +3797,7 @@
|
|
|
3745
3797
|
state.active = findLatestCreatedSessionName(state.sessions, preferredContainerName) || state.sessions[0].name;
|
|
3746
3798
|
}
|
|
3747
3799
|
if (state.active && state.active !== previousActive) {
|
|
3748
|
-
|
|
3800
|
+
ensureSessionNavPath(state.active);
|
|
3749
3801
|
state.pendingActiveSessionScroll = true;
|
|
3750
3802
|
}
|
|
3751
3803
|
if (state.terminal.sessionName && state.terminal.sessionName !== state.active) {
|
|
@@ -4683,6 +4735,37 @@
|
|
|
4683
4735
|
});
|
|
4684
4736
|
}
|
|
4685
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
|
+
|
|
4686
4769
|
window.addEventListener('keydown', function (event) {
|
|
4687
4770
|
if (event.key === 'Escape' && state.configModalOpen) {
|
|
4688
4771
|
closeConfigModal();
|
|
@@ -4703,6 +4786,9 @@
|
|
|
4703
4786
|
closeExternalLinkModalView();
|
|
4704
4787
|
syncUi();
|
|
4705
4788
|
}
|
|
4789
|
+
if (event.key === 'Escape' && state.cloneModal.open && !state.cloneModal.submitting) {
|
|
4790
|
+
closeCloneModal();
|
|
4791
|
+
}
|
|
4706
4792
|
if (event.key === 'Escape' && state.mobileSidebarOpen) {
|
|
4707
4793
|
closeMobileSessionPanel();
|
|
4708
4794
|
}
|
|
@@ -4813,7 +4899,7 @@
|
|
|
4813
4899
|
disconnectTerminal('', true);
|
|
4814
4900
|
});
|
|
4815
4901
|
|
|
4816
|
-
|
|
4902
|
+
loadSidebarNavState();
|
|
4817
4903
|
renderSessions();
|
|
4818
4904
|
renderMessages(state.messages);
|
|
4819
4905
|
setMobileSessionPanel(false);
|