@xcanwin/manyoyo 6.2.11 → 6.2.15
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 +85 -8
- package/lib/web/frontend/app.html +18 -1
- package/lib/web/frontend/app.js +171 -30
- package/lib/web/frontend/file-browser.js +34 -3
- package/package.json +1 -1
package/lib/web/frontend/app.css
CHANGED
|
@@ -272,6 +272,13 @@ textarea:focus-visible {
|
|
|
272
272
|
display: none !important;
|
|
273
273
|
}
|
|
274
274
|
|
|
275
|
+
/* showNotice/showConfirm/showPrompt 可能从已经打开的其它 modal(如目录选择器)内部触发,
|
|
276
|
+
和其它 .modal-backdrop 共用 z-index:80 时,谁在 DOM 里排后面谁盖住谁,容易被埋到底下;
|
|
277
|
+
这里给一个更高的 z-index,保证它始终浮在最上层 */
|
|
278
|
+
#genericDialogModal {
|
|
279
|
+
z-index: 90;
|
|
280
|
+
}
|
|
281
|
+
|
|
275
282
|
.modal {
|
|
276
283
|
width: min(960px, calc(100vw - 24px));
|
|
277
284
|
max-height: min(92vh, 860px);
|
|
@@ -326,6 +333,28 @@ textarea:focus-visible {
|
|
|
326
333
|
word-break: break-all;
|
|
327
334
|
}
|
|
328
335
|
|
|
336
|
+
.modal-message {
|
|
337
|
+
margin: 0;
|
|
338
|
+
color: var(--text);
|
|
339
|
+
font-size: 13px;
|
|
340
|
+
line-height: 1.6;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
.modal-dialog-input {
|
|
344
|
+
width: 100%;
|
|
345
|
+
margin-top: 10px;
|
|
346
|
+
border: 1px solid var(--line);
|
|
347
|
+
border-radius: 10px;
|
|
348
|
+
padding: 9px 11px;
|
|
349
|
+
background: var(--panel-strong);
|
|
350
|
+
color: var(--text);
|
|
351
|
+
font-size: 13px;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
.modal-dialog-input[hidden] {
|
|
355
|
+
display: none;
|
|
356
|
+
}
|
|
357
|
+
|
|
329
358
|
.modal-error {
|
|
330
359
|
margin-top: 10px;
|
|
331
360
|
color: var(--danger);
|
|
@@ -577,6 +606,9 @@ textarea:focus-visible {
|
|
|
577
606
|
|
|
578
607
|
.crumb-item:hover {
|
|
579
608
|
transform: none;
|
|
609
|
+
/* 不显式设置背景色的话,通用 button:hover 规则的深色背景会覆盖进来,
|
|
610
|
+
和同色系的文字撞色看不清,这里保持透明背景即可 */
|
|
611
|
+
background: transparent;
|
|
580
612
|
text-decoration: underline;
|
|
581
613
|
}
|
|
582
614
|
|
|
@@ -606,6 +638,11 @@ textarea:focus-visible {
|
|
|
606
638
|
border-radius: 999px;
|
|
607
639
|
}
|
|
608
640
|
|
|
641
|
+
.crumb-jump-active:hover {
|
|
642
|
+
/* 同 .crumb-item:hover,避免通用 button:hover 的深色背景和文字撞色 */
|
|
643
|
+
background: var(--panel-soft);
|
|
644
|
+
}
|
|
645
|
+
|
|
609
646
|
.session-card {
|
|
610
647
|
position: relative;
|
|
611
648
|
}
|
|
@@ -623,7 +660,7 @@ textarea:focus-visible {
|
|
|
623
660
|
|
|
624
661
|
.session-card-container .session-card-button,
|
|
625
662
|
.session-card-agent .session-card-button {
|
|
626
|
-
padding-right:
|
|
663
|
+
padding-right: 46px;
|
|
627
664
|
}
|
|
628
665
|
|
|
629
666
|
.session-card-button:hover {
|
|
@@ -761,18 +798,18 @@ textarea:focus-visible {
|
|
|
761
798
|
}
|
|
762
799
|
|
|
763
800
|
.tree-node-menu-trigger {
|
|
764
|
-
width:
|
|
765
|
-
height:
|
|
801
|
+
width: 30px;
|
|
802
|
+
height: 30px;
|
|
766
803
|
padding: 0;
|
|
767
804
|
display: inline-flex;
|
|
768
805
|
align-items: center;
|
|
769
806
|
justify-content: center;
|
|
770
|
-
border-radius:
|
|
807
|
+
border-radius: 9px;
|
|
771
808
|
}
|
|
772
809
|
|
|
773
810
|
.tree-node-menu-trigger svg {
|
|
774
|
-
width:
|
|
775
|
-
height:
|
|
811
|
+
width: 16px;
|
|
812
|
+
height: 16px;
|
|
776
813
|
fill: currentColor;
|
|
777
814
|
}
|
|
778
815
|
|
|
@@ -1181,7 +1218,7 @@ body.workspace-switcher-open .workspace-switcher-panel {
|
|
|
1181
1218
|
align-items: flex-start;
|
|
1182
1219
|
justify-content: space-between;
|
|
1183
1220
|
gap: 12px;
|
|
1184
|
-
flex-wrap:
|
|
1221
|
+
flex-wrap: nowrap;
|
|
1185
1222
|
padding: 10px 12px;
|
|
1186
1223
|
border-bottom: 1px solid var(--line);
|
|
1187
1224
|
background: rgba(255, 251, 244, 0.96);
|
|
@@ -1203,6 +1240,11 @@ body.workspace-switcher-open .workspace-switcher-panel {
|
|
|
1203
1240
|
margin-left: auto;
|
|
1204
1241
|
}
|
|
1205
1242
|
|
|
1243
|
+
.files-back-btn {
|
|
1244
|
+
display: none;
|
|
1245
|
+
flex-shrink: 0;
|
|
1246
|
+
}
|
|
1247
|
+
|
|
1206
1248
|
.files-preview-title {
|
|
1207
1249
|
font-family: var(--font-display);
|
|
1208
1250
|
font-size: 14px;
|
|
@@ -2125,7 +2167,7 @@ body.composer-options-open .composer-options-panel {
|
|
|
2125
2167
|
|
|
2126
2168
|
.files-layout {
|
|
2127
2169
|
grid-template-columns: minmax(0, 1fr);
|
|
2128
|
-
grid-template-rows: minmax(
|
|
2170
|
+
grid-template-rows: minmax(0, 1fr);
|
|
2129
2171
|
}
|
|
2130
2172
|
|
|
2131
2173
|
.files-sidebar {
|
|
@@ -2133,6 +2175,41 @@ body.composer-options-open .composer-options-panel {
|
|
|
2133
2175
|
border-bottom: 1px solid var(--line);
|
|
2134
2176
|
}
|
|
2135
2177
|
|
|
2178
|
+
.files-browser[data-mobile-pane="list"] .files-preview {
|
|
2179
|
+
display: none;
|
|
2180
|
+
}
|
|
2181
|
+
|
|
2182
|
+
.files-browser[data-mobile-pane="preview"] .files-sidebar {
|
|
2183
|
+
display: none;
|
|
2184
|
+
}
|
|
2185
|
+
|
|
2186
|
+
.files-browser[data-mobile-pane="preview"] .files-toolbar {
|
|
2187
|
+
display: none;
|
|
2188
|
+
}
|
|
2189
|
+
|
|
2190
|
+
.files-browser[data-mobile-pane="preview"] .files-back-btn {
|
|
2191
|
+
display: inline-flex;
|
|
2192
|
+
order: 1;
|
|
2193
|
+
}
|
|
2194
|
+
|
|
2195
|
+
.files-preview-head {
|
|
2196
|
+
flex-wrap: wrap;
|
|
2197
|
+
align-items: center;
|
|
2198
|
+
}
|
|
2199
|
+
|
|
2200
|
+
.files-preview-actions {
|
|
2201
|
+
order: 2;
|
|
2202
|
+
}
|
|
2203
|
+
|
|
2204
|
+
.files-preview-head-main {
|
|
2205
|
+
order: 3;
|
|
2206
|
+
flex-basis: 100%;
|
|
2207
|
+
}
|
|
2208
|
+
|
|
2209
|
+
.files-preview-meta {
|
|
2210
|
+
display: none;
|
|
2211
|
+
}
|
|
2212
|
+
|
|
2136
2213
|
.composer-inner {
|
|
2137
2214
|
align-items: stretch;
|
|
2138
2215
|
}
|
|
@@ -222,7 +222,7 @@
|
|
|
222
222
|
<button type="button" id="removeConfirmCancelBtn" class="secondary">关闭</button>
|
|
223
223
|
</header>
|
|
224
224
|
<div class="modal-body">
|
|
225
|
-
<p id="removeConfirmMessage"></p>
|
|
225
|
+
<p id="removeConfirmMessage" class="modal-message"></p>
|
|
226
226
|
</div>
|
|
227
227
|
<footer class="modal-footer">
|
|
228
228
|
<button type="button" id="removeConfirmKeepHistoryBtn" class="secondary">否,保留历史</button>
|
|
@@ -231,6 +231,23 @@
|
|
|
231
231
|
</section>
|
|
232
232
|
</div>
|
|
233
233
|
|
|
234
|
+
<div id="genericDialogModal" class="modal-backdrop" hidden>
|
|
235
|
+
<section class="modal modal-sm" role="dialog" aria-modal="true" aria-labelledby="genericDialogTitle">
|
|
236
|
+
<header class="modal-header">
|
|
237
|
+
<h2 id="genericDialogTitle"></h2>
|
|
238
|
+
<button type="button" id="genericDialogCloseBtn" class="secondary">关闭</button>
|
|
239
|
+
</header>
|
|
240
|
+
<div class="modal-body">
|
|
241
|
+
<p id="genericDialogMessage" class="modal-message"></p>
|
|
242
|
+
<input type="text" id="genericDialogInput" class="modal-dialog-input" hidden />
|
|
243
|
+
</div>
|
|
244
|
+
<footer class="modal-footer">
|
|
245
|
+
<button type="button" id="genericDialogCancelBtn" class="secondary" hidden>取消</button>
|
|
246
|
+
<button type="button" id="genericDialogConfirmBtn">确定</button>
|
|
247
|
+
</footer>
|
|
248
|
+
</section>
|
|
249
|
+
</div>
|
|
250
|
+
|
|
234
251
|
<div id="directoryPickerModal" class="modal-backdrop" hidden>
|
|
235
252
|
<section class="modal dir-picker-modal" role="dialog" aria-modal="true" aria-labelledby="directoryPickerTitle">
|
|
236
253
|
<header class="modal-header">
|
package/lib/web/frontend/app.js
CHANGED
|
@@ -185,6 +185,13 @@
|
|
|
185
185
|
const removeConfirmCancelBtn = document.getElementById('removeConfirmCancelBtn');
|
|
186
186
|
const removeConfirmKeepHistoryBtn = document.getElementById('removeConfirmKeepHistoryBtn');
|
|
187
187
|
const removeConfirmWithHistoryBtn = document.getElementById('removeConfirmWithHistoryBtn');
|
|
188
|
+
const genericDialogModal = document.getElementById('genericDialogModal');
|
|
189
|
+
const genericDialogTitle = document.getElementById('genericDialogTitle');
|
|
190
|
+
const genericDialogMessage = document.getElementById('genericDialogMessage');
|
|
191
|
+
const genericDialogInput = document.getElementById('genericDialogInput');
|
|
192
|
+
const genericDialogCloseBtn = document.getElementById('genericDialogCloseBtn');
|
|
193
|
+
const genericDialogCancelBtn = document.getElementById('genericDialogCancelBtn');
|
|
194
|
+
const genericDialogConfirmBtn = document.getElementById('genericDialogConfirmBtn');
|
|
188
195
|
const directoryPickerModal = document.getElementById('directoryPickerModal');
|
|
189
196
|
const directoryPickerTitle = document.getElementById('directoryPickerTitle');
|
|
190
197
|
const directoryPickerTip = document.getElementById('directoryPickerTip');
|
|
@@ -1336,7 +1343,7 @@
|
|
|
1336
1343
|
}
|
|
1337
1344
|
const detail = await ensureActiveSessionDetail();
|
|
1338
1345
|
if (!detail) {
|
|
1339
|
-
|
|
1346
|
+
await showNotice(state.sessionDetailError || '当前会话详情暂时不可用');
|
|
1340
1347
|
return;
|
|
1341
1348
|
}
|
|
1342
1349
|
state.agentTemplateModalOpen = true;
|
|
@@ -1606,7 +1613,7 @@
|
|
|
1606
1613
|
}
|
|
1607
1614
|
const detail = await ensureActiveSessionDetail();
|
|
1608
1615
|
if (!detail) {
|
|
1609
|
-
|
|
1616
|
+
await showNotice(state.sessionDetailError || '当前会话详情暂时不可用');
|
|
1610
1617
|
return;
|
|
1611
1618
|
}
|
|
1612
1619
|
state.modelModalOpen = true;
|
|
@@ -2641,7 +2648,7 @@
|
|
|
2641
2648
|
state.configModalOpen = true;
|
|
2642
2649
|
setModalVisible(configModal, true);
|
|
2643
2650
|
} catch (e) {
|
|
2644
|
-
|
|
2651
|
+
await showNotice(e.message);
|
|
2645
2652
|
} finally {
|
|
2646
2653
|
state.configLoading = false;
|
|
2647
2654
|
syncUi();
|
|
@@ -2696,7 +2703,7 @@
|
|
|
2696
2703
|
state.createModalOpen = true;
|
|
2697
2704
|
setModalVisible(createModal, true);
|
|
2698
2705
|
} catch (e) {
|
|
2699
|
-
|
|
2706
|
+
await showNotice(e.message);
|
|
2700
2707
|
} finally {
|
|
2701
2708
|
state.createLoading = false;
|
|
2702
2709
|
syncUi();
|
|
@@ -2828,7 +2835,7 @@
|
|
|
2828
2835
|
if (picker.loading || !picker.currentPath) {
|
|
2829
2836
|
return;
|
|
2830
2837
|
}
|
|
2831
|
-
const input =
|
|
2838
|
+
const input = await showPrompt('请输入新目录名称');
|
|
2832
2839
|
const name = String(input || '').trim();
|
|
2833
2840
|
if (!name) {
|
|
2834
2841
|
return;
|
|
@@ -2934,8 +2941,8 @@
|
|
|
2934
2941
|
Promise.all([
|
|
2935
2942
|
loadMessagesForSession(sessionName),
|
|
2936
2943
|
loadSessionDetailForSession(sessionName)
|
|
2937
|
-
]).catch(function (e) {
|
|
2938
|
-
|
|
2944
|
+
]).catch(async function (e) {
|
|
2945
|
+
await showNotice(e.message);
|
|
2939
2946
|
});
|
|
2940
2947
|
}
|
|
2941
2948
|
|
|
@@ -2961,7 +2968,7 @@
|
|
|
2961
2968
|
closeMobileSessionPanel();
|
|
2962
2969
|
}
|
|
2963
2970
|
} catch (e) {
|
|
2964
|
-
|
|
2971
|
+
await showNotice(e.message);
|
|
2965
2972
|
} finally {
|
|
2966
2973
|
state.creatingAgent = false;
|
|
2967
2974
|
syncUi();
|
|
@@ -3132,20 +3139,56 @@
|
|
|
3132
3139
|
panel.className = 'tree-node-menu-panel';
|
|
3133
3140
|
panel.hidden = true;
|
|
3134
3141
|
|
|
3142
|
+
// 面板展开时挂到 body 上用 position:fixed 定位,避免被 #sessionList 之类的
|
|
3143
|
+
// overflow:auto 滚动容器裁切(面板本来是靠 .tree-node-menu 的 position:absolute
|
|
3144
|
+
// 定位在卡片内的,一旦触发按钮接近滚动容器底部,面板就会被截断)。
|
|
3145
|
+
// 调用时 panel 必须已经是可见状态(display 不为 none),否则 offsetHeight 测不出真实高度。
|
|
3146
|
+
function positionPanel() {
|
|
3147
|
+
const rect = trigger.getBoundingClientRect();
|
|
3148
|
+
panel.style.position = 'fixed';
|
|
3149
|
+
panel.style.left = 'auto';
|
|
3150
|
+
panel.style.right = (window.innerWidth - rect.right) + 'px';
|
|
3151
|
+
panel.style.top = (rect.bottom + 4) + 'px';
|
|
3152
|
+
panel.style.bottom = 'auto';
|
|
3153
|
+
const panelHeight = panel.offsetHeight;
|
|
3154
|
+
if (rect.bottom + 4 + panelHeight > window.innerHeight) {
|
|
3155
|
+
// 下方空间不够时翻转到触发按钮上方展开,避免超出视口底部
|
|
3156
|
+
panel.style.top = 'auto';
|
|
3157
|
+
panel.style.bottom = (window.innerHeight - rect.top + 4) + 'px';
|
|
3158
|
+
}
|
|
3159
|
+
}
|
|
3160
|
+
|
|
3135
3161
|
function close() {
|
|
3136
3162
|
panel.hidden = true;
|
|
3137
3163
|
trigger.setAttribute('aria-expanded', 'false');
|
|
3138
3164
|
wrap.classList.remove('is-open');
|
|
3165
|
+
if (panel.parentNode === document.body) {
|
|
3166
|
+
wrap.appendChild(panel);
|
|
3167
|
+
}
|
|
3168
|
+
panel.style.position = '';
|
|
3169
|
+
panel.style.top = '';
|
|
3170
|
+
panel.style.bottom = '';
|
|
3171
|
+
panel.style.left = '';
|
|
3172
|
+
panel.style.right = '';
|
|
3139
3173
|
}
|
|
3140
3174
|
|
|
3141
3175
|
trigger.addEventListener('click', function (event) {
|
|
3142
3176
|
event.stopPropagation();
|
|
3143
3177
|
if (panel.hidden) {
|
|
3144
3178
|
closeOpenTreeNodeMenu();
|
|
3179
|
+
document.body.appendChild(panel);
|
|
3145
3180
|
panel.hidden = false;
|
|
3181
|
+
positionPanel();
|
|
3146
3182
|
trigger.setAttribute('aria-expanded', 'true');
|
|
3147
3183
|
wrap.classList.add('is-open');
|
|
3148
|
-
openTreeNodeMenu = {
|
|
3184
|
+
openTreeNodeMenu = {
|
|
3185
|
+
close: close,
|
|
3186
|
+
containsTarget: function (target) {
|
|
3187
|
+
// 面板展开时是 body 的直接子节点,不再是 wrap 的后代,
|
|
3188
|
+
// “点击外部关闭”判断要把它也算进来
|
|
3189
|
+
return wrap.contains(target) || panel.contains(target);
|
|
3190
|
+
}
|
|
3191
|
+
};
|
|
3149
3192
|
} else {
|
|
3150
3193
|
close();
|
|
3151
3194
|
openTreeNodeMenu = null;
|
|
@@ -3205,6 +3248,83 @@
|
|
|
3205
3248
|
});
|
|
3206
3249
|
}
|
|
3207
3250
|
|
|
3251
|
+
// 原生 alert/confirm/prompt 是同步阻塞的,会卡住整个页面(动画、其它交互全部停摆)。
|
|
3252
|
+
// 这里用一个通用的 Promise 化小弹窗承载 notice/confirm/prompt 三种形态,取代它们。
|
|
3253
|
+
function openGenericDialog(config) {
|
|
3254
|
+
const opts = config && typeof config === 'object' ? config : {};
|
|
3255
|
+
const mode = opts.mode === 'prompt' || opts.mode === 'confirm' ? opts.mode : 'notice';
|
|
3256
|
+
if (!genericDialogModal) {
|
|
3257
|
+
if (mode === 'prompt') {
|
|
3258
|
+
return Promise.resolve(window.prompt(opts.message || '', opts.defaultValue || ''));
|
|
3259
|
+
}
|
|
3260
|
+
if (mode === 'confirm') {
|
|
3261
|
+
return Promise.resolve(confirm(opts.message || ''));
|
|
3262
|
+
}
|
|
3263
|
+
alert(opts.message || '');
|
|
3264
|
+
return Promise.resolve();
|
|
3265
|
+
}
|
|
3266
|
+
return new Promise(function (resolve) {
|
|
3267
|
+
genericDialogTitle.textContent = opts.title
|
|
3268
|
+
|| (mode === 'notice' ? '提示' : (mode === 'confirm' ? '请确认' : '请输入'));
|
|
3269
|
+
genericDialogMessage.textContent = opts.message || '';
|
|
3270
|
+
genericDialogCancelBtn.hidden = mode === 'notice';
|
|
3271
|
+
genericDialogInput.hidden = mode !== 'prompt';
|
|
3272
|
+
genericDialogInput.value = mode === 'prompt' ? String(opts.defaultValue || '') : '';
|
|
3273
|
+
genericDialogConfirmBtn.textContent = mode === 'notice' ? '知道了' : '确定';
|
|
3274
|
+
|
|
3275
|
+
let settled = false;
|
|
3276
|
+
function resolveValue() {
|
|
3277
|
+
if (mode === 'confirm') return true;
|
|
3278
|
+
if (mode === 'prompt') return genericDialogInput.value;
|
|
3279
|
+
return undefined;
|
|
3280
|
+
}
|
|
3281
|
+
function cancelValue() {
|
|
3282
|
+
if (mode === 'confirm') return false;
|
|
3283
|
+
if (mode === 'prompt') return null;
|
|
3284
|
+
return undefined;
|
|
3285
|
+
}
|
|
3286
|
+
function finish(value) {
|
|
3287
|
+
if (settled) return;
|
|
3288
|
+
settled = true;
|
|
3289
|
+
setModalVisible(genericDialogModal, false);
|
|
3290
|
+
genericDialogConfirmBtn.removeEventListener('click', onConfirm);
|
|
3291
|
+
genericDialogCancelBtn.removeEventListener('click', onCancel);
|
|
3292
|
+
genericDialogCloseBtn.removeEventListener('click', onCancel);
|
|
3293
|
+
genericDialogInput.removeEventListener('keydown', onInputKeydown);
|
|
3294
|
+
resolve(value);
|
|
3295
|
+
}
|
|
3296
|
+
function onConfirm() { finish(resolveValue()); }
|
|
3297
|
+
function onCancel() { finish(cancelValue()); }
|
|
3298
|
+
function onInputKeydown(event) {
|
|
3299
|
+
if (event.key === 'Enter') {
|
|
3300
|
+
event.preventDefault();
|
|
3301
|
+
onConfirm();
|
|
3302
|
+
}
|
|
3303
|
+
}
|
|
3304
|
+
genericDialogConfirmBtn.addEventListener('click', onConfirm);
|
|
3305
|
+
genericDialogCancelBtn.addEventListener('click', onCancel);
|
|
3306
|
+
genericDialogCloseBtn.addEventListener('click', onCancel);
|
|
3307
|
+
genericDialogInput.addEventListener('keydown', onInputKeydown);
|
|
3308
|
+
setModalVisible(genericDialogModal, true);
|
|
3309
|
+
if (mode === 'prompt') {
|
|
3310
|
+
genericDialogInput.focus();
|
|
3311
|
+
genericDialogInput.select();
|
|
3312
|
+
}
|
|
3313
|
+
});
|
|
3314
|
+
}
|
|
3315
|
+
|
|
3316
|
+
function showNotice(message, title) {
|
|
3317
|
+
return openGenericDialog({ mode: 'notice', message: message, title: title });
|
|
3318
|
+
}
|
|
3319
|
+
|
|
3320
|
+
function showConfirm(message, title) {
|
|
3321
|
+
return openGenericDialog({ mode: 'confirm', message: message, title: title });
|
|
3322
|
+
}
|
|
3323
|
+
|
|
3324
|
+
function showPrompt(message, defaultValue, title) {
|
|
3325
|
+
return openGenericDialog({ mode: 'prompt', message: message, defaultValue: defaultValue, title: title });
|
|
3326
|
+
}
|
|
3327
|
+
|
|
3208
3328
|
async function removeContainerByName(containerName) {
|
|
3209
3329
|
const target = String(containerName || '').trim();
|
|
3210
3330
|
if (!target) return;
|
|
@@ -3230,7 +3350,7 @@
|
|
|
3230
3350
|
reloadMessages: wasActiveContainer
|
|
3231
3351
|
});
|
|
3232
3352
|
} catch (e) {
|
|
3233
|
-
|
|
3353
|
+
await showNotice(e.message);
|
|
3234
3354
|
}
|
|
3235
3355
|
}
|
|
3236
3356
|
|
|
@@ -3260,7 +3380,7 @@
|
|
|
3260
3380
|
reloadMessages: isActive
|
|
3261
3381
|
});
|
|
3262
3382
|
} catch (e) {
|
|
3263
|
-
|
|
3383
|
+
await showNotice(e.message);
|
|
3264
3384
|
}
|
|
3265
3385
|
}
|
|
3266
3386
|
|
|
@@ -3344,6 +3464,9 @@
|
|
|
3344
3464
|
if (!sessionBreadcrumb) {
|
|
3345
3465
|
return;
|
|
3346
3466
|
}
|
|
3467
|
+
// 展开中的菜单面板会被挂到 body 上(见 createTreeNodeMenu),
|
|
3468
|
+
// 这里要重建 DOM 之前先关掉,否则旧面板会变成脱离 wrap 的孤儿节点
|
|
3469
|
+
closeOpenTreeNodeMenu();
|
|
3347
3470
|
sessionBreadcrumb.innerHTML = '';
|
|
3348
3471
|
|
|
3349
3472
|
const rootCrumb = document.createElement('button');
|
|
@@ -3431,6 +3554,7 @@
|
|
|
3431
3554
|
}
|
|
3432
3555
|
|
|
3433
3556
|
function renderContainerLevel(groups) {
|
|
3557
|
+
closeOpenTreeNodeMenu();
|
|
3434
3558
|
sessionList.innerHTML = '';
|
|
3435
3559
|
if (!groups.length) {
|
|
3436
3560
|
const empty = document.createElement('div');
|
|
@@ -3445,10 +3569,10 @@
|
|
|
3445
3569
|
kind: 'container',
|
|
3446
3570
|
className: status.tone === 'history' ? 'tree-node-tone-history' : '',
|
|
3447
3571
|
title: containerGroup.containerRemark || containerGroup.containerName,
|
|
3448
|
-
meta: containerGroup.containerRemark ? containerGroup.containerName :
|
|
3572
|
+
meta: containerGroup.containerRemark ? containerGroup.containerName : containerGroup.hostPath,
|
|
3449
3573
|
statusLabel: status.label,
|
|
3450
3574
|
statusTone: status.tone,
|
|
3451
|
-
subMeta: containerGroup.hostPath,
|
|
3575
|
+
subMeta: containerGroup.containerRemark ? containerGroup.hostPath : '',
|
|
3452
3576
|
hasActive: containerContainsActiveSession(containerGroup),
|
|
3453
3577
|
onClick: function () {
|
|
3454
3578
|
navigateTo('agents', { container: containerGroup.containerName });
|
|
@@ -3493,7 +3617,7 @@
|
|
|
3493
3617
|
|
|
3494
3618
|
async function editContainerRemark(containerGroup) {
|
|
3495
3619
|
const current = containerGroup.containerRemark || '';
|
|
3496
|
-
const input =
|
|
3620
|
+
const input = await showPrompt('设置容器备注(留空清除备注)', current);
|
|
3497
3621
|
if (input === null) {
|
|
3498
3622
|
return;
|
|
3499
3623
|
}
|
|
@@ -3505,13 +3629,13 @@
|
|
|
3505
3629
|
});
|
|
3506
3630
|
await refreshSessionsSilent({ preferredName: state.active });
|
|
3507
3631
|
} catch (e) {
|
|
3508
|
-
|
|
3632
|
+
await showNotice(e.message);
|
|
3509
3633
|
}
|
|
3510
3634
|
}
|
|
3511
3635
|
|
|
3512
3636
|
async function editAgentRemark(session) {
|
|
3513
3637
|
const current = session.agentRemark || '';
|
|
3514
|
-
const input =
|
|
3638
|
+
const input = await showPrompt('设置 AGENT 备注(留空清除备注)', current);
|
|
3515
3639
|
if (input === null) {
|
|
3516
3640
|
return;
|
|
3517
3641
|
}
|
|
@@ -3523,11 +3647,12 @@
|
|
|
3523
3647
|
});
|
|
3524
3648
|
await refreshSessionsSilent({ preferredName: state.active });
|
|
3525
3649
|
} catch (e) {
|
|
3526
|
-
|
|
3650
|
+
await showNotice(e.message);
|
|
3527
3651
|
}
|
|
3528
3652
|
}
|
|
3529
3653
|
|
|
3530
3654
|
function renderAgentLevel(containerGroup) {
|
|
3655
|
+
closeOpenTreeNodeMenu();
|
|
3531
3656
|
sessionList.innerHTML = '';
|
|
3532
3657
|
const allSessions = containerGroup && Array.isArray(containerGroup.sessions) ? containerGroup.sessions : [];
|
|
3533
3658
|
const sessions = allSessions.filter(function (session) { return !session.synthetic; });
|
|
@@ -3762,7 +3887,7 @@
|
|
|
3762
3887
|
closeCloneModal();
|
|
3763
3888
|
await loadSessions(data.name);
|
|
3764
3889
|
if (data.resumeMayFail) {
|
|
3765
|
-
|
|
3890
|
+
await showNotice('复制完成。注意:AGENT 对话历史已复制,但底层 CLI 会话续接可能失败,如遇失败请以新对话继续。');
|
|
3766
3891
|
}
|
|
3767
3892
|
} catch (e) {
|
|
3768
3893
|
state.cloneModal.submitting = false;
|
|
@@ -4467,8 +4592,8 @@
|
|
|
4467
4592
|
if (agentTemplateBtn) {
|
|
4468
4593
|
agentTemplateBtn.addEventListener('click', function () {
|
|
4469
4594
|
closeComposerOptionsMenu();
|
|
4470
|
-
openAgentTemplateModal().catch(function (e) {
|
|
4471
|
-
|
|
4595
|
+
openAgentTemplateModal().catch(async function (e) {
|
|
4596
|
+
await showNotice(e && e.message ? e.message : '加载 Agent 模板失败');
|
|
4472
4597
|
});
|
|
4473
4598
|
});
|
|
4474
4599
|
}
|
|
@@ -4476,8 +4601,8 @@
|
|
|
4476
4601
|
if (activityModelChip) {
|
|
4477
4602
|
activityModelChip.addEventListener('click', function () {
|
|
4478
4603
|
closeComposerOptionsMenu();
|
|
4479
|
-
openModelModal().catch(function (e) {
|
|
4480
|
-
|
|
4604
|
+
openModelModal().catch(async function (e) {
|
|
4605
|
+
await showNotice(e && e.message ? e.message : '加载模型信息失败');
|
|
4481
4606
|
});
|
|
4482
4607
|
});
|
|
4483
4608
|
}
|
|
@@ -4711,7 +4836,7 @@
|
|
|
4711
4836
|
body: JSON.stringify({})
|
|
4712
4837
|
});
|
|
4713
4838
|
} catch (e) {
|
|
4714
|
-
|
|
4839
|
+
await showNotice(e.message);
|
|
4715
4840
|
state.agentRun.stopping = false;
|
|
4716
4841
|
syncUi();
|
|
4717
4842
|
}
|
|
@@ -4773,7 +4898,7 @@
|
|
|
4773
4898
|
if (mode === 'agent') {
|
|
4774
4899
|
const recovered = await recoverAgentRunFromServer(submitSession);
|
|
4775
4900
|
if (!recovered && document.visibilityState !== 'hidden') {
|
|
4776
|
-
|
|
4901
|
+
await showNotice(e.message);
|
|
4777
4902
|
}
|
|
4778
4903
|
} else {
|
|
4779
4904
|
if (state.active === submitSession) {
|
|
@@ -4782,7 +4907,7 @@
|
|
|
4782
4907
|
});
|
|
4783
4908
|
renderMessages(state.messages);
|
|
4784
4909
|
}
|
|
4785
|
-
|
|
4910
|
+
await showNotice(e.message);
|
|
4786
4911
|
}
|
|
4787
4912
|
} finally {
|
|
4788
4913
|
state.sending = false;
|
|
@@ -4973,6 +5098,14 @@
|
|
|
4973
5098
|
});
|
|
4974
5099
|
}
|
|
4975
5100
|
|
|
5101
|
+
if (genericDialogModal) {
|
|
5102
|
+
genericDialogModal.addEventListener('click', function (event) {
|
|
5103
|
+
if (event.target === genericDialogModal) {
|
|
5104
|
+
genericDialogCloseBtn.click();
|
|
5105
|
+
}
|
|
5106
|
+
});
|
|
5107
|
+
}
|
|
5108
|
+
|
|
4976
5109
|
if (directoryPickerModal) {
|
|
4977
5110
|
directoryPickerModal.addEventListener('click', function (event) {
|
|
4978
5111
|
if (event.target === directoryPickerModal && !state.directoryPicker.loading) {
|
|
@@ -5069,6 +5202,12 @@
|
|
|
5069
5202
|
if (event.key === 'Escape' && openTreeNodeMenu) {
|
|
5070
5203
|
closeOpenTreeNodeMenu();
|
|
5071
5204
|
}
|
|
5205
|
+
if (event.key === 'Escape' && removeConfirmModal && !removeConfirmModal.hidden) {
|
|
5206
|
+
removeConfirmCancelBtn.click();
|
|
5207
|
+
}
|
|
5208
|
+
if (event.key === 'Escape' && genericDialogModal && !genericDialogModal.hidden) {
|
|
5209
|
+
genericDialogCloseBtn.click();
|
|
5210
|
+
}
|
|
5072
5211
|
});
|
|
5073
5212
|
|
|
5074
5213
|
function onLayoutMediaChange() {
|
|
@@ -5126,7 +5265,7 @@
|
|
|
5126
5265
|
if (composerOptionsPanel && composerOptionsPanel.contains(target)) return;
|
|
5127
5266
|
closeComposerOptionsMenu();
|
|
5128
5267
|
}
|
|
5129
|
-
if (openTreeNodeMenu && !openTreeNodeMenu.
|
|
5268
|
+
if (openTreeNodeMenu && !openTreeNodeMenu.containsTarget(target)) {
|
|
5130
5269
|
closeOpenTreeNodeMenu();
|
|
5131
5270
|
}
|
|
5132
5271
|
});
|
|
@@ -5158,8 +5297,10 @@
|
|
|
5158
5297
|
root: filesPanel,
|
|
5159
5298
|
api,
|
|
5160
5299
|
onError: function (message) {
|
|
5161
|
-
|
|
5162
|
-
}
|
|
5300
|
+
showNotice(message);
|
|
5301
|
+
},
|
|
5302
|
+
confirmFn: showConfirm,
|
|
5303
|
+
promptFn: showPrompt
|
|
5163
5304
|
});
|
|
5164
5305
|
}
|
|
5165
5306
|
|
|
@@ -5173,7 +5314,7 @@
|
|
|
5173
5314
|
setMobileSessionPanel(false);
|
|
5174
5315
|
document.body.classList.add('agent-mode');
|
|
5175
5316
|
syncUi();
|
|
5176
|
-
loadSessions().catch(function (e) {
|
|
5177
|
-
|
|
5317
|
+
loadSessions().catch(async function (e) {
|
|
5318
|
+
await showNotice(e.message);
|
|
5178
5319
|
});
|
|
5179
5320
|
})();
|
|
@@ -75,6 +75,14 @@
|
|
|
75
75
|
const onError = options && typeof options.onError === 'function'
|
|
76
76
|
? options.onError
|
|
77
77
|
: function (message) { window.alert(message); };
|
|
78
|
+
// confirmFn/promptFn 可由调用方注入非阻塞的自定义弹窗,缺省退回原生 confirm/prompt
|
|
79
|
+
// (和上面 onError 的兜底写法保持一致)。
|
|
80
|
+
const confirmFn = options && typeof options.confirmFn === 'function'
|
|
81
|
+
? options.confirmFn
|
|
82
|
+
: function (message) { return Promise.resolve(window.confirm(message)); };
|
|
83
|
+
const promptFn = options && typeof options.promptFn === 'function'
|
|
84
|
+
? options.promptFn
|
|
85
|
+
: function (message, defaultValue) { return Promise.resolve(window.prompt(message, defaultValue)); };
|
|
78
86
|
if (!root || typeof api !== 'function') {
|
|
79
87
|
return {
|
|
80
88
|
sync: function () {}
|
|
@@ -99,6 +107,7 @@
|
|
|
99
107
|
</aside>
|
|
100
108
|
<section class="files-preview">
|
|
101
109
|
<header class="files-preview-head">
|
|
110
|
+
<button type="button" class="secondary files-back-btn" data-action="back-to-list">← 返回列表</button>
|
|
102
111
|
<div class="files-preview-head-main">
|
|
103
112
|
<div class="files-preview-title" data-role="preview-title">未选择文件</div>
|
|
104
113
|
<div class="files-preview-meta" data-role="preview-meta">请选择左侧文件或目录</div>
|
|
@@ -120,10 +129,12 @@
|
|
|
120
129
|
const previewTitleNode = root.querySelector('[data-role="preview-title"]');
|
|
121
130
|
const previewMetaNode = root.querySelector('[data-role="preview-meta"]');
|
|
122
131
|
const previewBodyNode = root.querySelector('[data-role="preview-body"]');
|
|
132
|
+
const filesBrowserNode = root.querySelector('.files-browser');
|
|
123
133
|
const visitBtn = root.querySelector('[data-action="visit"]');
|
|
124
134
|
const mkdirBtn = root.querySelector('[data-action="mkdir"]');
|
|
125
135
|
const saveBtn = root.querySelector('[data-action="save"]');
|
|
126
136
|
const toggleMarkdownViewBtn = root.querySelector('[data-action="toggle-markdown-view"]');
|
|
137
|
+
const backToListBtn = root.querySelector('[data-action="back-to-list"]');
|
|
127
138
|
|
|
128
139
|
const state = {
|
|
129
140
|
visible: false,
|
|
@@ -147,9 +158,17 @@
|
|
|
147
158
|
editorHost: null,
|
|
148
159
|
previewReadOnly: true,
|
|
149
160
|
previewDirty: false,
|
|
150
|
-
markdownViewMode: 'rendered'
|
|
161
|
+
markdownViewMode: 'rendered',
|
|
162
|
+
mobileActivePane: 'list'
|
|
151
163
|
};
|
|
152
164
|
|
|
165
|
+
function setMobilePane(pane) {
|
|
166
|
+
state.mobileActivePane = pane === 'preview' ? 'preview' : 'list';
|
|
167
|
+
if (filesBrowserNode) {
|
|
168
|
+
filesBrowserNode.setAttribute('data-mobile-pane', state.mobileActivePane);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
153
172
|
function setStatus(text) {
|
|
154
173
|
if (statusNode) {
|
|
155
174
|
statusNode.textContent = String(text || '').trim() || '就绪';
|
|
@@ -351,12 +370,14 @@
|
|
|
351
370
|
listNode.innerHTML = '';
|
|
352
371
|
|
|
353
372
|
if (!state.sessionName) {
|
|
373
|
+
setMobilePane('list');
|
|
354
374
|
listNode.innerHTML = '<div class="files-empty">请选择左侧会话后再浏览容器文件。</div>';
|
|
355
375
|
renderPreviewEmpty('未选择会话', '请选择左侧会话后再浏览容器文件。');
|
|
356
376
|
setStatus('未选择会话');
|
|
357
377
|
return;
|
|
358
378
|
}
|
|
359
379
|
if (state.historyOnly) {
|
|
380
|
+
setMobilePane('list');
|
|
360
381
|
listNode.innerHTML = '<div class="files-empty">当前会话只有历史记录,没有可访问的运行中容器。</div>';
|
|
361
382
|
renderPreviewEmpty('容器不可用', '当前会话只有历史记录,没有可访问的运行中容器。');
|
|
362
383
|
setStatus('容器不可用');
|
|
@@ -421,6 +442,7 @@
|
|
|
421
442
|
state.pathDraft = pathText;
|
|
422
443
|
state.selectedPath = '';
|
|
423
444
|
state.selectedEntry = null;
|
|
445
|
+
setMobilePane('list');
|
|
424
446
|
renderList();
|
|
425
447
|
try {
|
|
426
448
|
const payload = await api('/api/sessions/' + encodeURIComponent(state.sessionName) + '/fs/list?path=' + encodeURIComponent(pathText));
|
|
@@ -455,7 +477,7 @@
|
|
|
455
477
|
const fileSize = Number(selectedEntry && selectedEntry.size);
|
|
456
478
|
const requiresReadonlyConfirm = Number.isFinite(fileSize) && fileSize >= FILE_EDIT_MAX_BYTES;
|
|
457
479
|
if (requiresReadonlyConfirm) {
|
|
458
|
-
const yes =
|
|
480
|
+
const yes = await confirmFn(`文件较大(${formatBytes(fileSize)}),继续后将以只读方式全量预览,无法保存。是否继续?`);
|
|
459
481
|
if (!yes) {
|
|
460
482
|
return;
|
|
461
483
|
}
|
|
@@ -466,6 +488,7 @@
|
|
|
466
488
|
state.selectedPath = pathText;
|
|
467
489
|
state.selectedEntry = selectedEntry;
|
|
468
490
|
state.markdownViewMode = 'rendered';
|
|
491
|
+
setMobilePane('preview');
|
|
469
492
|
renderList();
|
|
470
493
|
renderPreviewEmpty(pathText, '正在读取文件内容...');
|
|
471
494
|
try {
|
|
@@ -553,7 +576,7 @@
|
|
|
553
576
|
if (!state.sessionName || state.historyOnly === true || state.loadingList || state.loadingFile) {
|
|
554
577
|
return;
|
|
555
578
|
}
|
|
556
|
-
const input =
|
|
579
|
+
const input = await promptFn('请输入新目录名称');
|
|
557
580
|
const name = String(input || '').trim();
|
|
558
581
|
if (!name) {
|
|
559
582
|
return;
|
|
@@ -601,6 +624,7 @@
|
|
|
601
624
|
state.entries = [];
|
|
602
625
|
state.selectedPath = '';
|
|
603
626
|
state.selectedEntry = null;
|
|
627
|
+
setMobilePane('list');
|
|
604
628
|
renderPreviewEmpty('未选择文件', '请选择左侧文件进行预览。');
|
|
605
629
|
} else if (containerPathChanged) {
|
|
606
630
|
state.containerPath = nextContainerPath;
|
|
@@ -660,6 +684,13 @@
|
|
|
660
684
|
});
|
|
661
685
|
}
|
|
662
686
|
|
|
687
|
+
if (backToListBtn) {
|
|
688
|
+
backToListBtn.addEventListener('click', function () {
|
|
689
|
+
setMobilePane('list');
|
|
690
|
+
});
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
setMobilePane('list');
|
|
663
694
|
renderList();
|
|
664
695
|
|
|
665
696
|
return {
|