@xcanwin/manyoyo 5.9.2 → 5.9.11
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/README.md +1 -0
- package/bin/manyoyo.js +19 -5
- package/lib/web/frontend/app.css +121 -55
- package/lib/web/frontend/app.html +16 -9
- package/lib/web/frontend/app.js +320 -46
- package/lib/web/frontend/codemirror-entry.js +13 -0
- package/lib/web/frontend/codemirror.bundle.js +13 -0
- package/lib/web/frontend/file-browser.js +220 -29
- package/lib/web/server.js +230 -15
- package/package.json +1 -1
package/lib/web/frontend/app.js
CHANGED
|
@@ -57,8 +57,10 @@
|
|
|
57
57
|
configSaveMessage: '',
|
|
58
58
|
createLoading: false,
|
|
59
59
|
createSubmitting: false,
|
|
60
|
+
creatingAgent: false,
|
|
60
61
|
agentTemplateSaving: false,
|
|
61
62
|
configSnapshot: null,
|
|
63
|
+
configEditor: null,
|
|
62
64
|
sessionDetail: null,
|
|
63
65
|
sessionDetailError: '',
|
|
64
66
|
sessionDetailRequestId: 0,
|
|
@@ -81,6 +83,8 @@
|
|
|
81
83
|
title: '',
|
|
82
84
|
tip: '',
|
|
83
85
|
currentPath: '',
|
|
86
|
+
pathDraft: '',
|
|
87
|
+
parentPath: '',
|
|
84
88
|
entries: [],
|
|
85
89
|
error: ''
|
|
86
90
|
},
|
|
@@ -131,6 +135,7 @@
|
|
|
131
135
|
const sidebarBackdrop = document.getElementById('sidebarBackdrop');
|
|
132
136
|
const openConfigBtn = document.getElementById('openConfigBtn');
|
|
133
137
|
const openCreateBtn = document.getElementById('openCreateBtn');
|
|
138
|
+
const openCreateMenuBtn = document.getElementById('openCreateMenuBtn');
|
|
134
139
|
const configModal = document.getElementById('configModal');
|
|
135
140
|
const configModalTitle = document.getElementById('configModalTitle');
|
|
136
141
|
const configPath = document.getElementById('configPath');
|
|
@@ -165,11 +170,13 @@
|
|
|
165
170
|
const directoryPickerModal = document.getElementById('directoryPickerModal');
|
|
166
171
|
const directoryPickerTitle = document.getElementById('directoryPickerTitle');
|
|
167
172
|
const directoryPickerTip = document.getElementById('directoryPickerTip');
|
|
168
|
-
const
|
|
173
|
+
const directoryPickerPathInput = document.getElementById('directoryPickerPathInput');
|
|
174
|
+
const directoryPickerVisitBtn = document.getElementById('directoryPickerVisitBtn');
|
|
175
|
+
const directoryPickerStatus = document.getElementById('directoryPickerStatus');
|
|
176
|
+
const directoryPickerMkdirBtn = document.getElementById('directoryPickerMkdirBtn');
|
|
169
177
|
const directoryPickerList = document.getElementById('directoryPickerList');
|
|
170
178
|
const directoryPickerError = document.getElementById('directoryPickerError');
|
|
171
179
|
const directoryPickerCancelBtn = document.getElementById('directoryPickerCancelBtn');
|
|
172
|
-
const directoryPickerUpBtn = document.getElementById('directoryPickerUpBtn');
|
|
173
180
|
const directoryPickerSelectBtn = document.getElementById('directoryPickerSelectBtn');
|
|
174
181
|
const activeTitle = document.getElementById('activeTitle');
|
|
175
182
|
const activeMeta = document.getElementById('activeMeta');
|
|
@@ -808,6 +815,70 @@
|
|
|
808
815
|
directoryPickerError.textContent = text;
|
|
809
816
|
}
|
|
810
817
|
|
|
818
|
+
function ensureConfigCodeEditor() {
|
|
819
|
+
if (!configEditor || state.configEditor || !window.ManyoyoCodeEditor || typeof window.ManyoyoCodeEditor.create !== 'function') {
|
|
820
|
+
return state.configEditor;
|
|
821
|
+
}
|
|
822
|
+
state.configEditor = window.ManyoyoCodeEditor.create(configEditor, {
|
|
823
|
+
doc: '',
|
|
824
|
+
language: 'javascript',
|
|
825
|
+
readOnly: true,
|
|
826
|
+
onChange: function () {
|
|
827
|
+
if (!state.configSaveMessage) {
|
|
828
|
+
return;
|
|
829
|
+
}
|
|
830
|
+
state.configSaveMessage = '';
|
|
831
|
+
showConfigStatus('');
|
|
832
|
+
}
|
|
833
|
+
});
|
|
834
|
+
return state.configEditor;
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
function setConfigEditorValue(value, readOnly) {
|
|
838
|
+
const text = String(value == null ? '' : value);
|
|
839
|
+
const editor = ensureConfigCodeEditor();
|
|
840
|
+
if (editor) {
|
|
841
|
+
editor.setValue(text);
|
|
842
|
+
editor.setLanguage('javascript');
|
|
843
|
+
editor.setReadOnly(readOnly !== false);
|
|
844
|
+
return;
|
|
845
|
+
}
|
|
846
|
+
if ('value' in configEditor) {
|
|
847
|
+
configEditor.value = text;
|
|
848
|
+
configEditor.readOnly = readOnly !== false;
|
|
849
|
+
} else {
|
|
850
|
+
configEditor.textContent = text;
|
|
851
|
+
}
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
function getConfigEditorValue() {
|
|
855
|
+
const editor = ensureConfigCodeEditor();
|
|
856
|
+
if (editor) {
|
|
857
|
+
return editor.getValue();
|
|
858
|
+
}
|
|
859
|
+
if ('value' in configEditor) {
|
|
860
|
+
return configEditor.value || '';
|
|
861
|
+
}
|
|
862
|
+
return configEditor && configEditor.textContent ? configEditor.textContent : '';
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
function setDirectoryPickerStatus(message) {
|
|
866
|
+
if (!directoryPickerStatus) return;
|
|
867
|
+
directoryPickerStatus.textContent = String(message || '').trim() || '未加载';
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
function joinDirectoryPath(basePath, childName) {
|
|
871
|
+
const base = String(basePath || '/').trim() || '/';
|
|
872
|
+
const child = String(childName || '').trim();
|
|
873
|
+
if (!child) {
|
|
874
|
+
return base;
|
|
875
|
+
}
|
|
876
|
+
if (base === '/') {
|
|
877
|
+
return '/' + child.replace(/^\/+/, '');
|
|
878
|
+
}
|
|
879
|
+
return base.replace(/\/+$/, '') + '/' + child.replace(/^\/+/, '');
|
|
880
|
+
}
|
|
881
|
+
|
|
811
882
|
function envMapToText(envMap) {
|
|
812
883
|
if (!envMap || typeof envMap !== 'object') {
|
|
813
884
|
return '';
|
|
@@ -2193,10 +2264,11 @@
|
|
|
2193
2264
|
}
|
|
2194
2265
|
|
|
2195
2266
|
const activeAgentRunning = isAgentRunActiveForSession(state.active) || hasPendingAgentMessagesForSession(state.active);
|
|
2196
|
-
const busy = state.loadingSessions || state.loadingMessages || state.sending;
|
|
2267
|
+
const busy = state.loadingSessions || state.loadingMessages || state.sending || state.creatingAgent;
|
|
2197
2268
|
refreshBtn.disabled = busy;
|
|
2198
2269
|
if (addAgentBtn) {
|
|
2199
2270
|
addAgentBtn.disabled = !state.active || busy;
|
|
2271
|
+
addAgentBtn.textContent = state.creatingAgent ? '新建中...' : '新建 AGENT';
|
|
2200
2272
|
}
|
|
2201
2273
|
removeBtn.disabled = !state.active || busy;
|
|
2202
2274
|
removeAllBtn.disabled = !state.active || busy;
|
|
@@ -2221,6 +2293,9 @@
|
|
|
2221
2293
|
if (openCreateBtn) {
|
|
2222
2294
|
openCreateBtn.disabled = state.createLoading || state.createSubmitting;
|
|
2223
2295
|
}
|
|
2296
|
+
if (openCreateMenuBtn) {
|
|
2297
|
+
openCreateMenuBtn.disabled = state.createLoading || state.createSubmitting;
|
|
2298
|
+
}
|
|
2224
2299
|
if (openConfigBtn) {
|
|
2225
2300
|
openConfigBtn.disabled = state.configLoading || state.configSaving;
|
|
2226
2301
|
}
|
|
@@ -2251,6 +2326,9 @@
|
|
|
2251
2326
|
if (!state.active) {
|
|
2252
2327
|
sendState.textContent = '未选择会话';
|
|
2253
2328
|
sendState.classList.remove('is-active');
|
|
2329
|
+
} else if (state.creatingAgent) {
|
|
2330
|
+
sendState.textContent = '正在新建 AGENT…';
|
|
2331
|
+
sendState.classList.add('is-active');
|
|
2254
2332
|
} else if (activeAgentRunning && agentMode) {
|
|
2255
2333
|
sendState.textContent = state.agentRun.stopping ? '正在停止 Agent…' : 'Agent 执行中';
|
|
2256
2334
|
sendState.classList.add('is-active');
|
|
@@ -2304,6 +2382,8 @@
|
|
|
2304
2382
|
);
|
|
2305
2383
|
if (!state.active) {
|
|
2306
2384
|
sendState.textContent = '未选择会话';
|
|
2385
|
+
} else if (state.creatingAgent) {
|
|
2386
|
+
sendState.textContent = '正在新建 AGENT…';
|
|
2307
2387
|
} else if (agentMode && !agentEnabled) {
|
|
2308
2388
|
sendState.textContent = '当前会话未配置 AGENT 模板';
|
|
2309
2389
|
} else if (state.sending) {
|
|
@@ -2313,7 +2393,7 @@
|
|
|
2313
2393
|
} else {
|
|
2314
2394
|
sendState.textContent = '就绪';
|
|
2315
2395
|
}
|
|
2316
|
-
sendState.classList.toggle('is-active', state.sending);
|
|
2396
|
+
sendState.classList.toggle('is-active', state.sending || state.creatingAgent);
|
|
2317
2397
|
if (composer) {
|
|
2318
2398
|
composer.hidden = !activityTab;
|
|
2319
2399
|
}
|
|
@@ -2437,8 +2517,7 @@
|
|
|
2437
2517
|
configPath.textContent = lines.filter(Boolean).join('\n');
|
|
2438
2518
|
}
|
|
2439
2519
|
if (configEditor) {
|
|
2440
|
-
|
|
2441
|
-
configEditor.value = typeof config.raw === 'string' ? config.raw : '';
|
|
2520
|
+
setConfigEditorValue(typeof config.raw === 'string' ? config.raw : '', config.editable === false);
|
|
2442
2521
|
}
|
|
2443
2522
|
}
|
|
2444
2523
|
|
|
@@ -2482,7 +2561,7 @@
|
|
|
2482
2561
|
try {
|
|
2483
2562
|
await api('/api/config', {
|
|
2484
2563
|
method: 'PUT',
|
|
2485
|
-
body: JSON.stringify({ raw:
|
|
2564
|
+
body: JSON.stringify({ raw: getConfigEditorValue() })
|
|
2486
2565
|
});
|
|
2487
2566
|
const config = await fetchConfigSnapshot();
|
|
2488
2567
|
renderConfigModalSnapshot(config);
|
|
@@ -2542,39 +2621,73 @@
|
|
|
2542
2621
|
if (directoryPickerTip) {
|
|
2543
2622
|
directoryPickerTip.textContent = picker.tip || '';
|
|
2544
2623
|
}
|
|
2545
|
-
if (
|
|
2546
|
-
|
|
2624
|
+
if (directoryPickerPathInput) {
|
|
2625
|
+
directoryPickerPathInput.value = picker.pathDraft || picker.currentPath || '/';
|
|
2547
2626
|
}
|
|
2548
2627
|
showDirectoryPickerError(picker.error);
|
|
2549
|
-
if (
|
|
2550
|
-
|
|
2628
|
+
if (directoryPickerVisitBtn) {
|
|
2629
|
+
directoryPickerVisitBtn.disabled = picker.loading || !(picker.pathDraft || picker.currentPath);
|
|
2630
|
+
}
|
|
2631
|
+
if (directoryPickerMkdirBtn) {
|
|
2632
|
+
directoryPickerMkdirBtn.disabled = picker.loading || !picker.currentPath;
|
|
2551
2633
|
}
|
|
2552
2634
|
if (directoryPickerSelectBtn) {
|
|
2553
2635
|
directoryPickerSelectBtn.disabled = picker.loading || !picker.currentPath;
|
|
2554
2636
|
}
|
|
2637
|
+
if (picker.loading) {
|
|
2638
|
+
setDirectoryPickerStatus('读取目录中');
|
|
2639
|
+
} else if (picker.entries.length) {
|
|
2640
|
+
setDirectoryPickerStatus('共 ' + picker.entries.length + ' 项');
|
|
2641
|
+
} else if (picker.currentPath) {
|
|
2642
|
+
setDirectoryPickerStatus('共 0 项');
|
|
2643
|
+
} else {
|
|
2644
|
+
setDirectoryPickerStatus('未加载');
|
|
2645
|
+
}
|
|
2555
2646
|
if (!directoryPickerList) {
|
|
2556
2647
|
return;
|
|
2557
2648
|
}
|
|
2558
2649
|
directoryPickerList.innerHTML = '';
|
|
2559
2650
|
if (picker.loading) {
|
|
2560
2651
|
const loading = document.createElement('div');
|
|
2561
|
-
loading.className = 'empty';
|
|
2652
|
+
loading.className = 'files-empty';
|
|
2562
2653
|
loading.textContent = '目录加载中...';
|
|
2563
2654
|
directoryPickerList.appendChild(loading);
|
|
2564
2655
|
return;
|
|
2565
2656
|
}
|
|
2657
|
+
if (picker.parentPath) {
|
|
2658
|
+
const parentButton = document.createElement('button');
|
|
2659
|
+
parentButton.type = 'button';
|
|
2660
|
+
parentButton.className = 'files-entry files-entry-parent';
|
|
2661
|
+
parentButton.title = picker.parentPath;
|
|
2662
|
+
parentButton.innerHTML = `
|
|
2663
|
+
<span class="files-entry-name">
|
|
2664
|
+
<span class="files-entry-title">..</span>
|
|
2665
|
+
</span>
|
|
2666
|
+
<span class="files-entry-meta">上一级</span>
|
|
2667
|
+
`;
|
|
2668
|
+
parentButton.addEventListener('click', function () {
|
|
2669
|
+
loadDirectoryPicker(picker.parentPath);
|
|
2670
|
+
});
|
|
2671
|
+
directoryPickerList.appendChild(parentButton);
|
|
2672
|
+
}
|
|
2566
2673
|
if (!picker.entries.length) {
|
|
2567
2674
|
const empty = document.createElement('div');
|
|
2568
|
-
empty.className = 'empty';
|
|
2569
|
-
empty.
|
|
2675
|
+
empty.className = 'files-empty';
|
|
2676
|
+
empty.innerHTML = ' ';
|
|
2570
2677
|
directoryPickerList.appendChild(empty);
|
|
2571
2678
|
return;
|
|
2572
2679
|
}
|
|
2573
2680
|
picker.entries.forEach(function (entry) {
|
|
2574
2681
|
const btn = document.createElement('button');
|
|
2575
2682
|
btn.type = 'button';
|
|
2576
|
-
btn.className = '
|
|
2577
|
-
btn.
|
|
2683
|
+
btn.className = 'files-entry';
|
|
2684
|
+
btn.title = String(entry.path || entry.name || '');
|
|
2685
|
+
btn.innerHTML = `
|
|
2686
|
+
<span class="files-entry-name">
|
|
2687
|
+
<span class="files-entry-title">${escapeHtml(entry.name || entry.path || '未命名')}</span>
|
|
2688
|
+
</span>
|
|
2689
|
+
<span class="files-entry-meta">目录</span>
|
|
2690
|
+
`;
|
|
2578
2691
|
btn.addEventListener('click', function () {
|
|
2579
2692
|
loadDirectoryPicker(entry.path);
|
|
2580
2693
|
});
|
|
@@ -2586,15 +2699,15 @@
|
|
|
2586
2699
|
const picker = state.directoryPicker;
|
|
2587
2700
|
picker.loading = true;
|
|
2588
2701
|
picker.error = '';
|
|
2589
|
-
|
|
2590
|
-
|
|
2591
|
-
}
|
|
2702
|
+
const nextPath = String(targetPath || picker.pathDraft || picker.currentPath || '/').trim() || '/';
|
|
2703
|
+
picker.pathDraft = nextPath;
|
|
2592
2704
|
renderDirectoryPicker();
|
|
2593
2705
|
try {
|
|
2594
2706
|
const params = new URLSearchParams();
|
|
2595
|
-
params.set('path',
|
|
2707
|
+
params.set('path', nextPath);
|
|
2596
2708
|
const data = await api('/api/fs/directories?' + params.toString());
|
|
2597
|
-
picker.currentPath = data.currentPath ||
|
|
2709
|
+
picker.currentPath = data.currentPath || nextPath;
|
|
2710
|
+
picker.pathDraft = picker.currentPath;
|
|
2598
2711
|
picker.parentPath = data.parentPath || '';
|
|
2599
2712
|
picker.entries = Array.isArray(data.entries) ? data.entries : [];
|
|
2600
2713
|
} catch (e) {
|
|
@@ -2606,6 +2719,32 @@
|
|
|
2606
2719
|
}
|
|
2607
2720
|
}
|
|
2608
2721
|
|
|
2722
|
+
async function createDirectoryInPicker() {
|
|
2723
|
+
const picker = state.directoryPicker;
|
|
2724
|
+
if (picker.loading || !picker.currentPath) {
|
|
2725
|
+
return;
|
|
2726
|
+
}
|
|
2727
|
+
const input = window.prompt('请输入新目录名称');
|
|
2728
|
+
const name = String(input || '').trim();
|
|
2729
|
+
if (!name) {
|
|
2730
|
+
return;
|
|
2731
|
+
}
|
|
2732
|
+
picker.loading = true;
|
|
2733
|
+
picker.error = '';
|
|
2734
|
+
renderDirectoryPicker();
|
|
2735
|
+
try {
|
|
2736
|
+
await api('/api/fs/directories/mkdir', {
|
|
2737
|
+
method: 'POST',
|
|
2738
|
+
body: JSON.stringify({ path: joinDirectoryPath(picker.currentPath, name) })
|
|
2739
|
+
});
|
|
2740
|
+
await loadDirectoryPicker(picker.currentPath);
|
|
2741
|
+
} catch (e) {
|
|
2742
|
+
picker.loading = false;
|
|
2743
|
+
picker.error = e && e.message ? e.message : '创建目录失败';
|
|
2744
|
+
renderDirectoryPicker();
|
|
2745
|
+
}
|
|
2746
|
+
}
|
|
2747
|
+
|
|
2609
2748
|
function closeDirectoryPicker() {
|
|
2610
2749
|
state.directoryPicker.open = false;
|
|
2611
2750
|
state.directoryPicker.loading = false;
|
|
@@ -2613,6 +2752,7 @@
|
|
|
2613
2752
|
state.directoryPicker.title = '';
|
|
2614
2753
|
state.directoryPicker.tip = '';
|
|
2615
2754
|
state.directoryPicker.currentPath = '';
|
|
2755
|
+
state.directoryPicker.pathDraft = '';
|
|
2616
2756
|
state.directoryPicker.parentPath = '';
|
|
2617
2757
|
state.directoryPicker.entries = [];
|
|
2618
2758
|
state.directoryPicker.error = '';
|
|
@@ -2640,6 +2780,7 @@
|
|
|
2640
2780
|
picker.title = '选择 hostPath';
|
|
2641
2781
|
picker.tip = '浏览宿主机目录,选中后会回填 create 表单。';
|
|
2642
2782
|
picker.currentPath = (createHostPath.value || '').trim() || '/';
|
|
2783
|
+
picker.pathDraft = picker.currentPath;
|
|
2643
2784
|
renderDirectoryPicker();
|
|
2644
2785
|
loadDirectoryPicker(picker.currentPath);
|
|
2645
2786
|
}
|
|
@@ -2699,6 +2840,11 @@
|
|
|
2699
2840
|
if (!targetContainer) {
|
|
2700
2841
|
return;
|
|
2701
2842
|
}
|
|
2843
|
+
if (state.creatingAgent) {
|
|
2844
|
+
return;
|
|
2845
|
+
}
|
|
2846
|
+
state.creatingAgent = true;
|
|
2847
|
+
syncUi();
|
|
2702
2848
|
try {
|
|
2703
2849
|
const data = await api('/api/sessions/' + encodeURIComponent(targetContainer) + '/agents', {
|
|
2704
2850
|
method: 'POST',
|
|
@@ -2712,6 +2858,9 @@
|
|
|
2712
2858
|
}
|
|
2713
2859
|
} catch (e) {
|
|
2714
2860
|
alert(e.message);
|
|
2861
|
+
} finally {
|
|
2862
|
+
state.creatingAgent = false;
|
|
2863
|
+
syncUi();
|
|
2715
2864
|
}
|
|
2716
2865
|
}
|
|
2717
2866
|
|
|
@@ -2755,9 +2904,7 @@
|
|
|
2755
2904
|
});
|
|
2756
2905
|
group.containers.forEach(function (containerGroup) {
|
|
2757
2906
|
containerGroup.sessions.sort(function (a, b) {
|
|
2758
|
-
|
|
2759
|
-
const timeB = b && b.updatedAt ? new Date(b.updatedAt).getTime() : 0;
|
|
2760
|
-
return timeB - timeA;
|
|
2907
|
+
return compareSessionByCreatedDesc(a, b);
|
|
2761
2908
|
});
|
|
2762
2909
|
});
|
|
2763
2910
|
return group;
|
|
@@ -2772,6 +2919,108 @@
|
|
|
2772
2919
|
return (parts || []).filter(Boolean).join(' · ');
|
|
2773
2920
|
}
|
|
2774
2921
|
|
|
2922
|
+
function getSessionCreatedTime(session) {
|
|
2923
|
+
if (session && session.createdAt) {
|
|
2924
|
+
const time = new Date(session.createdAt).getTime();
|
|
2925
|
+
if (Number.isFinite(time)) {
|
|
2926
|
+
return time;
|
|
2927
|
+
}
|
|
2928
|
+
}
|
|
2929
|
+
return 0;
|
|
2930
|
+
}
|
|
2931
|
+
|
|
2932
|
+
function getSessionUpdatedTime(session) {
|
|
2933
|
+
if (session && session.updatedAt) {
|
|
2934
|
+
const time = new Date(session.updatedAt).getTime();
|
|
2935
|
+
if (Number.isFinite(time)) {
|
|
2936
|
+
return time;
|
|
2937
|
+
}
|
|
2938
|
+
}
|
|
2939
|
+
return 0;
|
|
2940
|
+
}
|
|
2941
|
+
|
|
2942
|
+
function getSessionAgentCreationRank(session) {
|
|
2943
|
+
const agentId = session && session.agentId ? String(session.agentId) : '';
|
|
2944
|
+
if (!agentId || agentId === 'default') {
|
|
2945
|
+
return 1;
|
|
2946
|
+
}
|
|
2947
|
+
const matched = agentId.match(/^agent-(\d+)$/);
|
|
2948
|
+
return matched ? (Number(matched[1]) || 0) : 0;
|
|
2949
|
+
}
|
|
2950
|
+
|
|
2951
|
+
function compareSessionByCreatedDesc(a, b) {
|
|
2952
|
+
const createdA = getSessionCreatedTime(a);
|
|
2953
|
+
const createdB = getSessionCreatedTime(b);
|
|
2954
|
+
if (createdA !== createdB) {
|
|
2955
|
+
return createdB - createdA;
|
|
2956
|
+
}
|
|
2957
|
+
if (a && b && a.containerName && a.containerName === b.containerName) {
|
|
2958
|
+
const rankA = getSessionAgentCreationRank(a);
|
|
2959
|
+
const rankB = getSessionAgentCreationRank(b);
|
|
2960
|
+
if (rankA !== rankB) {
|
|
2961
|
+
return rankB - rankA;
|
|
2962
|
+
}
|
|
2963
|
+
}
|
|
2964
|
+
const updatedA = getSessionUpdatedTime(a);
|
|
2965
|
+
const updatedB = getSessionUpdatedTime(b);
|
|
2966
|
+
if (updatedA !== updatedB) {
|
|
2967
|
+
return updatedB - updatedA;
|
|
2968
|
+
}
|
|
2969
|
+
return String((a && a.name) || '').localeCompare(String((b && b.name) || ''), 'zh-CN');
|
|
2970
|
+
}
|
|
2971
|
+
|
|
2972
|
+
function findLatestCreatedSessionName(sessions, preferredContainerName) {
|
|
2973
|
+
const list = Array.isArray(sessions) ? sessions.filter(Boolean) : [];
|
|
2974
|
+
if (!list.length) {
|
|
2975
|
+
return '';
|
|
2976
|
+
}
|
|
2977
|
+
const targetContainer = String(preferredContainerName || '').trim();
|
|
2978
|
+
const scoped = targetContainer
|
|
2979
|
+
? list.filter(function (session) {
|
|
2980
|
+
return session && session.containerName === targetContainer;
|
|
2981
|
+
})
|
|
2982
|
+
: list;
|
|
2983
|
+
const candidates = scoped.length ? scoped : list;
|
|
2984
|
+
const sorted = candidates.slice().sort(compareSessionByCreatedDesc);
|
|
2985
|
+
return sorted.length && sorted[0] && sorted[0].name ? sorted[0].name : '';
|
|
2986
|
+
}
|
|
2987
|
+
|
|
2988
|
+
function findPreferredSessionNameAfterRemoval(sessions, removedName) {
|
|
2989
|
+
const removedSessionName = String(removedName || '').trim();
|
|
2990
|
+
if (!removedSessionName) {
|
|
2991
|
+
return '';
|
|
2992
|
+
}
|
|
2993
|
+
const removedRef = parseSessionKey(removedSessionName);
|
|
2994
|
+
const remaining = (Array.isArray(sessions) ? sessions : []).filter(function (session) {
|
|
2995
|
+
return session
|
|
2996
|
+
&& session.name
|
|
2997
|
+
&& session.name !== removedSessionName
|
|
2998
|
+
&& session.containerName === removedRef.containerName;
|
|
2999
|
+
});
|
|
3000
|
+
if (!remaining.length) {
|
|
3001
|
+
return '';
|
|
3002
|
+
}
|
|
3003
|
+
|
|
3004
|
+
const removedRank = getSessionAgentCreationRank({ agentId: removedRef.agentId });
|
|
3005
|
+
if (removedRef.agentId && removedRef.agentId !== WEB_DEFAULT_AGENT_ID && removedRank > 0) {
|
|
3006
|
+
const lowerRanked = remaining
|
|
3007
|
+
.filter(function (session) { return getSessionAgentCreationRank(session) < removedRank; })
|
|
3008
|
+
.sort(function (a, b) { return getSessionAgentCreationRank(b) - getSessionAgentCreationRank(a); });
|
|
3009
|
+
if (lowerRanked.length && lowerRanked[0] && lowerRanked[0].name) {
|
|
3010
|
+
return lowerRanked[0].name;
|
|
3011
|
+
}
|
|
3012
|
+
|
|
3013
|
+
const higherRanked = remaining
|
|
3014
|
+
.filter(function (session) { return getSessionAgentCreationRank(session) > removedRank; })
|
|
3015
|
+
.sort(function (a, b) { return getSessionAgentCreationRank(a) - getSessionAgentCreationRank(b); });
|
|
3016
|
+
if (higherRanked.length && higherRanked[0] && higherRanked[0].name) {
|
|
3017
|
+
return higherRanked[0].name;
|
|
3018
|
+
}
|
|
3019
|
+
}
|
|
3020
|
+
|
|
3021
|
+
return findLatestCreatedSessionName(remaining, removedRef.containerName);
|
|
3022
|
+
}
|
|
3023
|
+
|
|
2775
3024
|
function createDisclosureButton(expanded, label) {
|
|
2776
3025
|
const button = document.createElement('button');
|
|
2777
3026
|
button.type = 'button';
|
|
@@ -2916,7 +3165,7 @@
|
|
|
2916
3165
|
const addAgentBtn = document.createElement('button');
|
|
2917
3166
|
addAgentBtn.type = 'button';
|
|
2918
3167
|
addAgentBtn.className = 'secondary tree-node-menu-item';
|
|
2919
|
-
addAgentBtn.textContent = '新建AGENT';
|
|
3168
|
+
addAgentBtn.textContent = '新建 AGENT';
|
|
2920
3169
|
addAgentBtn.addEventListener('click', function (event) {
|
|
2921
3170
|
event.stopPropagation();
|
|
2922
3171
|
createAgentSession(containerName);
|
|
@@ -3057,12 +3306,15 @@
|
|
|
3057
3306
|
}
|
|
3058
3307
|
|
|
3059
3308
|
function renderSessions() {
|
|
3309
|
+
const directoryCount = new Set(state.sessions.map(function (session) {
|
|
3310
|
+
return String(session && session.hostPath ? session.hostPath : '').trim() || '未配置目录';
|
|
3311
|
+
}).filter(Boolean)).size;
|
|
3060
3312
|
const containerCount = new Set(state.sessions.map(function (session) {
|
|
3061
3313
|
return session && session.containerName ? session.containerName : '';
|
|
3062
3314
|
}).filter(Boolean)).size;
|
|
3063
3315
|
sessionCount.textContent = state.loadingSessions
|
|
3064
3316
|
? '加载中...'
|
|
3065
|
-
: `${
|
|
3317
|
+
: `${directoryCount} 个 目录 / ${containerCount} 个容器 / ${state.sessions.length} 个 AGENT`;
|
|
3066
3318
|
|
|
3067
3319
|
if (state.loadingSessions) {
|
|
3068
3320
|
renderSessionsLoading();
|
|
@@ -3302,7 +3554,7 @@
|
|
|
3302
3554
|
}
|
|
3303
3555
|
}
|
|
3304
3556
|
|
|
3305
|
-
function applySessionsSnapshot(rawSessions, preferredName) {
|
|
3557
|
+
function applySessionsSnapshot(rawSessions, preferredName, preferredContainerName) {
|
|
3306
3558
|
const previousActive = state.active;
|
|
3307
3559
|
state.sessions = Array.isArray(rawSessions) ? rawSessions : [];
|
|
3308
3560
|
pruneSidebarTreeState();
|
|
@@ -3317,7 +3569,7 @@
|
|
|
3317
3569
|
state.sessionDetailError = '';
|
|
3318
3570
|
}
|
|
3319
3571
|
if (!state.active && state.sessions.length) {
|
|
3320
|
-
state.active = state.sessions[0].name;
|
|
3572
|
+
state.active = findLatestCreatedSessionName(state.sessions, preferredContainerName) || state.sessions[0].name;
|
|
3321
3573
|
}
|
|
3322
3574
|
if (state.active && state.active !== previousActive) {
|
|
3323
3575
|
ensureSessionPathExpanded(state.active);
|
|
@@ -3341,7 +3593,7 @@
|
|
|
3341
3593
|
let requestError = null;
|
|
3342
3594
|
try {
|
|
3343
3595
|
const data = await api('/api/sessions');
|
|
3344
|
-
applySessionsSnapshot(data.sessions, opts.preferredName);
|
|
3596
|
+
applySessionsSnapshot(data.sessions, opts.preferredName, opts.preferredContainerName);
|
|
3345
3597
|
} catch (e) {
|
|
3346
3598
|
requestError = e;
|
|
3347
3599
|
} finally {
|
|
@@ -3767,6 +4019,13 @@
|
|
|
3767
4019
|
});
|
|
3768
4020
|
}
|
|
3769
4021
|
|
|
4022
|
+
if (openCreateMenuBtn) {
|
|
4023
|
+
openCreateMenuBtn.addEventListener('click', function () {
|
|
4024
|
+
closeMobileActionsMenu();
|
|
4025
|
+
openCreateModal();
|
|
4026
|
+
});
|
|
4027
|
+
}
|
|
4028
|
+
|
|
3770
4029
|
if (agentTemplateBtn) {
|
|
3771
4030
|
agentTemplateBtn.addEventListener('click', function () {
|
|
3772
4031
|
openAgentTemplateModal().catch(function (e) {
|
|
@@ -3794,16 +4053,6 @@
|
|
|
3794
4053
|
});
|
|
3795
4054
|
}
|
|
3796
4055
|
|
|
3797
|
-
if (configEditor) {
|
|
3798
|
-
configEditor.addEventListener('input', function () {
|
|
3799
|
-
if (!state.configSaveMessage) {
|
|
3800
|
-
return;
|
|
3801
|
-
}
|
|
3802
|
-
state.configSaveMessage = '';
|
|
3803
|
-
showConfigStatus('');
|
|
3804
|
-
});
|
|
3805
|
-
}
|
|
3806
|
-
|
|
3807
4056
|
if (createCancelBtn) {
|
|
3808
4057
|
createCancelBtn.addEventListener('click', function () {
|
|
3809
4058
|
closeCreateModal();
|
|
@@ -3829,14 +4078,32 @@
|
|
|
3829
4078
|
});
|
|
3830
4079
|
}
|
|
3831
4080
|
|
|
3832
|
-
if (
|
|
3833
|
-
|
|
3834
|
-
|
|
3835
|
-
|
|
4081
|
+
if (directoryPickerPathInput) {
|
|
4082
|
+
directoryPickerPathInput.addEventListener('input', function () {
|
|
4083
|
+
state.directoryPicker.pathDraft = directoryPickerPathInput.value;
|
|
4084
|
+
});
|
|
4085
|
+
directoryPickerPathInput.addEventListener('keydown', function (event) {
|
|
4086
|
+
if (event.key === 'Enter') {
|
|
4087
|
+
event.preventDefault();
|
|
4088
|
+
loadDirectoryPicker();
|
|
3836
4089
|
}
|
|
3837
4090
|
});
|
|
3838
4091
|
}
|
|
3839
4092
|
|
|
4093
|
+
if (directoryPickerVisitBtn) {
|
|
4094
|
+
directoryPickerVisitBtn.addEventListener('click', function () {
|
|
4095
|
+
loadDirectoryPicker();
|
|
4096
|
+
});
|
|
4097
|
+
}
|
|
4098
|
+
|
|
4099
|
+
if (directoryPickerMkdirBtn) {
|
|
4100
|
+
directoryPickerMkdirBtn.addEventListener('click', function () {
|
|
4101
|
+
createDirectoryInPicker().catch(function (e) {
|
|
4102
|
+
showDirectoryPickerError(e && e.message ? e.message : '创建目录失败');
|
|
4103
|
+
});
|
|
4104
|
+
});
|
|
4105
|
+
}
|
|
4106
|
+
|
|
3840
4107
|
if (directoryPickerSelectBtn) {
|
|
3841
4108
|
directoryPickerSelectBtn.addEventListener('click', function () {
|
|
3842
4109
|
applyPickedDirectory();
|
|
@@ -4347,14 +4614,21 @@
|
|
|
4347
4614
|
closeMobileActionsMenu();
|
|
4348
4615
|
const activeSession = getActiveSession();
|
|
4349
4616
|
const targetAgent = activeSession && activeSession.agentName ? activeSession.agentName : state.active;
|
|
4350
|
-
const yes = confirm('
|
|
4617
|
+
const yes = confirm('确认删除 AGENT ' + targetAgent + ' ?');
|
|
4351
4618
|
if (!yes) return;
|
|
4352
4619
|
try {
|
|
4353
4620
|
const current = state.active;
|
|
4354
|
-
|
|
4621
|
+
const targetContainerName = activeSession && activeSession.containerName ? activeSession.containerName : '';
|
|
4622
|
+
const fallbackSessionName = findPreferredSessionNameAfterRemoval(state.sessions, current);
|
|
4623
|
+
await api('/api/sessions/' + encodeURIComponent(state.active) + '/remove-with-history', {
|
|
4355
4624
|
method: 'POST'
|
|
4356
4625
|
});
|
|
4357
|
-
await
|
|
4626
|
+
await refreshSessions({
|
|
4627
|
+
preferredName: fallbackSessionName || '',
|
|
4628
|
+
preferredContainerName: targetContainerName,
|
|
4629
|
+
withLoading: true,
|
|
4630
|
+
reloadMessages: true
|
|
4631
|
+
});
|
|
4358
4632
|
} catch (e) {
|
|
4359
4633
|
alert(e.message);
|
|
4360
4634
|
}
|
|
@@ -35,8 +35,10 @@ function createEditor(parent, options = {}) {
|
|
|
35
35
|
const initialDoc = String(options.doc || '');
|
|
36
36
|
const initialLanguage = String(options.language || 'text').trim();
|
|
37
37
|
const initialReadOnly = options.readOnly !== false;
|
|
38
|
+
const onChange = typeof options.onChange === 'function' ? options.onChange : null;
|
|
38
39
|
const languageCompartment = new Compartment();
|
|
39
40
|
const readOnlyCompartment = new Compartment();
|
|
41
|
+
let suppressChange = false;
|
|
40
42
|
const view = new EditorView({
|
|
41
43
|
parent: target,
|
|
42
44
|
state: EditorState.create({
|
|
@@ -44,6 +46,12 @@ function createEditor(parent, options = {}) {
|
|
|
44
46
|
extensions: [
|
|
45
47
|
basicSetup,
|
|
46
48
|
EditorView.lineWrapping,
|
|
49
|
+
EditorView.updateListener.of(function (update) {
|
|
50
|
+
if (!update.docChanged || suppressChange || !onChange) {
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
onChange(update.state.doc.toString());
|
|
54
|
+
}),
|
|
47
55
|
readOnlyCompartment.of([
|
|
48
56
|
EditorState.readOnly.of(initialReadOnly),
|
|
49
57
|
EditorView.editable.of(!initialReadOnly)
|
|
@@ -65,6 +73,7 @@ function createEditor(parent, options = {}) {
|
|
|
65
73
|
return {
|
|
66
74
|
setValue(nextValue) {
|
|
67
75
|
const text = String(nextValue == null ? '' : nextValue);
|
|
76
|
+
suppressChange = true;
|
|
68
77
|
view.dispatch({
|
|
69
78
|
changes: {
|
|
70
79
|
from: 0,
|
|
@@ -72,6 +81,10 @@ function createEditor(parent, options = {}) {
|
|
|
72
81
|
insert: text
|
|
73
82
|
}
|
|
74
83
|
});
|
|
84
|
+
suppressChange = false;
|
|
85
|
+
},
|
|
86
|
+
getValue() {
|
|
87
|
+
return view.state.doc.toString();
|
|
75
88
|
},
|
|
76
89
|
setLanguage(nextLanguage) {
|
|
77
90
|
view.dispatch({
|