@xcanwin/manyoyo 5.7.2 → 5.7.4
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 +171 -2
- package/lib/web/frontend/app.html +34 -5
- package/lib/web/frontend/app.js +512 -167
- package/lib/web/server.js +577 -192
- package/package.json +1 -1
package/lib/web/frontend/app.js
CHANGED
|
@@ -63,6 +63,17 @@
|
|
|
63
63
|
createRuns: {},
|
|
64
64
|
sessionNodeMap: new Map(),
|
|
65
65
|
sessionRenderMode: 'empty',
|
|
66
|
+
directoryPicker: {
|
|
67
|
+
open: false,
|
|
68
|
+
loading: false,
|
|
69
|
+
mode: '',
|
|
70
|
+
title: '',
|
|
71
|
+
tip: '',
|
|
72
|
+
currentPath: '',
|
|
73
|
+
basePath: '',
|
|
74
|
+
entries: [],
|
|
75
|
+
error: ''
|
|
76
|
+
},
|
|
66
77
|
messageRequestId: 0,
|
|
67
78
|
agentRun: {
|
|
68
79
|
active: false,
|
|
@@ -104,6 +115,7 @@
|
|
|
104
115
|
const openConfigBtn = document.getElementById('openConfigBtn');
|
|
105
116
|
const openCreateBtn = document.getElementById('openCreateBtn');
|
|
106
117
|
const configModal = document.getElementById('configModal');
|
|
118
|
+
const configModalTitle = document.getElementById('configModalTitle');
|
|
107
119
|
const configPath = document.getElementById('configPath');
|
|
108
120
|
const configEditor = document.getElementById('configEditor');
|
|
109
121
|
const configError = document.getElementById('configError');
|
|
@@ -120,6 +132,8 @@
|
|
|
120
132
|
const createContainerName = document.getElementById('createContainerName');
|
|
121
133
|
const createHostPath = document.getElementById('createHostPath');
|
|
122
134
|
const createContainerPath = document.getElementById('createContainerPath');
|
|
135
|
+
const pickHostPathBtn = document.getElementById('pickHostPathBtn');
|
|
136
|
+
const pickContainerPathBtn = document.getElementById('pickContainerPathBtn');
|
|
123
137
|
const createImageName = document.getElementById('createImageName');
|
|
124
138
|
const createImageVersion = document.getElementById('createImageVersion');
|
|
125
139
|
const createContainerMode = document.getElementById('createContainerMode');
|
|
@@ -131,6 +145,15 @@
|
|
|
131
145
|
const createEnv = document.getElementById('createEnv');
|
|
132
146
|
const createEnvFile = document.getElementById('createEnvFile');
|
|
133
147
|
const createVolumes = document.getElementById('createVolumes');
|
|
148
|
+
const directoryPickerModal = document.getElementById('directoryPickerModal');
|
|
149
|
+
const directoryPickerTitle = document.getElementById('directoryPickerTitle');
|
|
150
|
+
const directoryPickerTip = document.getElementById('directoryPickerTip');
|
|
151
|
+
const directoryPickerCurrent = document.getElementById('directoryPickerCurrent');
|
|
152
|
+
const directoryPickerList = document.getElementById('directoryPickerList');
|
|
153
|
+
const directoryPickerError = document.getElementById('directoryPickerError');
|
|
154
|
+
const directoryPickerCancelBtn = document.getElementById('directoryPickerCancelBtn');
|
|
155
|
+
const directoryPickerUpBtn = document.getElementById('directoryPickerUpBtn');
|
|
156
|
+
const directoryPickerSelectBtn = document.getElementById('directoryPickerSelectBtn');
|
|
134
157
|
const activeTitle = document.getElementById('activeTitle');
|
|
135
158
|
const activeMeta = document.getElementById('activeMeta');
|
|
136
159
|
const activityCommandBtn = document.getElementById('activityCommandBtn');
|
|
@@ -436,6 +459,32 @@
|
|
|
436
459
|
});
|
|
437
460
|
}
|
|
438
461
|
|
|
462
|
+
function normalizeSlashPath(value) {
|
|
463
|
+
return String(value || '').replace(/\\/g, '/');
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
function isChildPath(basePath, targetPath) {
|
|
467
|
+
const normalizedBase = normalizeSlashPath(basePath).replace(/\/+$/, '');
|
|
468
|
+
const normalizedTarget = normalizeSlashPath(targetPath).replace(/\/+$/, '');
|
|
469
|
+
if (!normalizedBase) {
|
|
470
|
+
return false;
|
|
471
|
+
}
|
|
472
|
+
return normalizedTarget === normalizedBase || normalizedTarget.startsWith(normalizedBase + '/');
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
function buildContainerPathFromHostSelection(baseHostPath, baseContainerPath, selectedHostPath) {
|
|
476
|
+
const normalizedBaseHost = normalizeSlashPath(baseHostPath).replace(/\/+$/, '');
|
|
477
|
+
const normalizedContainer = normalizeSlashPath(baseContainerPath).replace(/\/+$/, '') || '/workspace';
|
|
478
|
+
const normalizedSelected = normalizeSlashPath(selectedHostPath).replace(/\/+$/, '');
|
|
479
|
+
if (!normalizedBaseHost || !isChildPath(normalizedBaseHost, normalizedSelected)) {
|
|
480
|
+
return normalizedContainer;
|
|
481
|
+
}
|
|
482
|
+
const relative = normalizedSelected === normalizedBaseHost
|
|
483
|
+
? ''
|
|
484
|
+
: normalizedSelected.slice(normalizedBaseHost.length + 1);
|
|
485
|
+
return relative ? `${normalizedContainer}/${relative}`.replace(/\/+/g, '/') : normalizedContainer;
|
|
486
|
+
}
|
|
487
|
+
|
|
439
488
|
function setModalVisible(modalNode, visible) {
|
|
440
489
|
if (!modalNode) return;
|
|
441
490
|
modalNode.hidden = !visible;
|
|
@@ -466,6 +515,18 @@
|
|
|
466
515
|
configError.textContent = text;
|
|
467
516
|
}
|
|
468
517
|
|
|
518
|
+
function showDirectoryPickerError(message) {
|
|
519
|
+
if (!directoryPickerError) return;
|
|
520
|
+
const text = String(message || '').trim();
|
|
521
|
+
if (!text) {
|
|
522
|
+
directoryPickerError.hidden = true;
|
|
523
|
+
directoryPickerError.textContent = '';
|
|
524
|
+
return;
|
|
525
|
+
}
|
|
526
|
+
directoryPickerError.hidden = false;
|
|
527
|
+
directoryPickerError.textContent = text;
|
|
528
|
+
}
|
|
529
|
+
|
|
469
530
|
function envMapToText(envMap) {
|
|
470
531
|
if (!envMap || typeof envMap !== 'object') {
|
|
471
532
|
return '';
|
|
@@ -621,9 +682,10 @@
|
|
|
621
682
|
createAgentPromptCommand.value = value.agentPromptCommand || '';
|
|
622
683
|
state.createAgentPromptAuto = false;
|
|
623
684
|
createYolo.value = value.yolo || '';
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
685
|
+
// 敏感 env 与继承数组由服务端在创建时合并,前端表单默认不回显,避免泄露或重复提交。
|
|
686
|
+
createEnv.value = '';
|
|
687
|
+
createEnvFile.value = '';
|
|
688
|
+
createVolumes.value = '';
|
|
627
689
|
updateCreateAgentPromptCommandFromCommand();
|
|
628
690
|
}
|
|
629
691
|
|
|
@@ -792,7 +854,8 @@
|
|
|
792
854
|
const status = sessionStatusInfo(session.status);
|
|
793
855
|
const messageCount = safeMessageCount(session.messageCount);
|
|
794
856
|
const updatedAt = formatDateTime(session.updatedAt) || '暂无更新';
|
|
795
|
-
|
|
857
|
+
const containerName = session.containerName || '未绑定容器';
|
|
858
|
+
return `${containerName} · ${status.label} · ${messageCount} 条对话 · ${updatedAt}`;
|
|
796
859
|
}
|
|
797
860
|
|
|
798
861
|
function buildMessageMetaLines(message) {
|
|
@@ -1236,9 +1299,9 @@
|
|
|
1236
1299
|
function renderSessionDetailPanels() {
|
|
1237
1300
|
const detail = state.sessionDetail;
|
|
1238
1301
|
if (!state.active) {
|
|
1239
|
-
renderEmptyInspector(detailSummary, '详情视图', '选择左侧会话后,这里会显示会话概览、Agent
|
|
1240
|
-
renderEmptyInspector(configSummary, '配置视图', '
|
|
1241
|
-
renderEmptyInspector(checkSummary, '检查视图', '
|
|
1302
|
+
renderEmptyInspector(detailSummary, '详情视图', '选择左侧会话后,这里会显示会话概览、Agent 运行状态与最近活动。');
|
|
1303
|
+
renderEmptyInspector(configSummary, '配置视图', '选择会话后可查看当前容器会话的生效配置摘要。');
|
|
1304
|
+
renderEmptyInspector(checkSummary, '检查视图', '选择会话后可查看当前会话的诊断结论与最近问题。');
|
|
1242
1305
|
return;
|
|
1243
1306
|
}
|
|
1244
1307
|
if (state.loadingSessionDetail) {
|
|
@@ -1258,55 +1321,98 @@
|
|
|
1258
1321
|
const applied = detail.applied || {};
|
|
1259
1322
|
const status = sessionStatusInfo(detail.status);
|
|
1260
1323
|
const updatedText = formatDateTime(detail.updatedAt) || '暂无更新';
|
|
1324
|
+
const lastResumeText = detail.lastResumeAt ? formatDateTime(detail.lastResumeAt) : '暂无';
|
|
1325
|
+
const latestTimestampText = detail.latestTimestamp ? formatDateTime(detail.latestTimestamp) : '暂无';
|
|
1326
|
+
const latestRoleMap = {
|
|
1327
|
+
user: '我',
|
|
1328
|
+
assistant: 'Agent',
|
|
1329
|
+
system: '系统'
|
|
1330
|
+
};
|
|
1331
|
+
const latestRoleLabel = latestRoleMap[String(detail.latestRole || '').toLowerCase()] || (detail.latestRole || '暂无');
|
|
1332
|
+
const imageVersionValid = /^\d+\.\d+\.\d+-[A-Za-z0-9][A-Za-z0-9_.-]*$/.test(String(applied.imageVersion || ''));
|
|
1333
|
+
let resumeStatusValue = '未执行';
|
|
1334
|
+
let resumeStatusTone = 'warn';
|
|
1335
|
+
let resumeStatusDetail = detail.resumeSupported
|
|
1336
|
+
? '支持 resume,但当前会话还没有最近一次执行记录。'
|
|
1337
|
+
: '当前 Agent 程序或模板不支持 resume。';
|
|
1338
|
+
if (detail.lastResumeOk === true) {
|
|
1339
|
+
resumeStatusValue = '最近成功';
|
|
1340
|
+
resumeStatusTone = 'ok';
|
|
1341
|
+
resumeStatusDetail = `最近一次 resume 成功,时间:${lastResumeText}。`;
|
|
1342
|
+
} else if (detail.lastResumeOk === false) {
|
|
1343
|
+
resumeStatusValue = '最近失败';
|
|
1344
|
+
resumeStatusTone = 'danger';
|
|
1345
|
+
resumeStatusDetail = detail.lastResumeError
|
|
1346
|
+
? `最近一次 resume 失败:${detail.lastResumeError}`
|
|
1347
|
+
: `最近一次 resume 失败,时间:${lastResumeText}。`;
|
|
1348
|
+
} else if (!detail.resumeSupported) {
|
|
1349
|
+
resumeStatusValue = '不支持';
|
|
1350
|
+
}
|
|
1351
|
+
|
|
1352
|
+
const commandEntries = [];
|
|
1353
|
+
if (applied.shellPrefix) {
|
|
1354
|
+
commandEntries.push({ label: 'shellPrefix', value: applied.shellPrefix });
|
|
1355
|
+
}
|
|
1356
|
+
if (applied.shell) {
|
|
1357
|
+
commandEntries.push({ label: 'shell', value: applied.shell });
|
|
1358
|
+
}
|
|
1359
|
+
if (applied.shellSuffix) {
|
|
1360
|
+
commandEntries.push({ label: 'shellSuffix', value: applied.shellSuffix });
|
|
1361
|
+
}
|
|
1362
|
+
if (applied.defaultCommand && applied.defaultCommand !== applied.shell) {
|
|
1363
|
+
commandEntries.push({ label: '启动命令', value: applied.defaultCommand });
|
|
1364
|
+
} else if (!applied.shell) {
|
|
1365
|
+
commandEntries.push({ label: '启动命令', value: applied.defaultCommand || '—' });
|
|
1366
|
+
}
|
|
1367
|
+
commandEntries.push({ label: 'Agent 模板', value: detail.agentPromptCommand || '—' });
|
|
1368
|
+
commandEntries.push({ label: 'yolo', value: applied.yolo || '—' });
|
|
1261
1369
|
|
|
1262
1370
|
if (detailSummary) {
|
|
1263
1371
|
detailSummary.innerHTML = '';
|
|
1264
1372
|
renderKeyValueCard(detailSummary, '会话概览', [
|
|
1265
|
-
{ label: '
|
|
1373
|
+
{ label: 'AGENT', value: detail.agentName || detail.name || state.active },
|
|
1374
|
+
{ label: '容器', value: detail.containerName || '—' },
|
|
1266
1375
|
{ label: '状态', value: status.label, tone: status.tone },
|
|
1267
1376
|
{ label: '镜像', value: detail.image || applied.imageName || '—' },
|
|
1268
1377
|
{ label: '最近更新', value: updatedText },
|
|
1269
1378
|
{ label: '消息数', value: String(safeMessageCount(detail.messageCount)) }
|
|
1270
1379
|
]);
|
|
1271
|
-
renderKeyValueCard(detailSummary, 'Agent
|
|
1380
|
+
renderKeyValueCard(detailSummary, 'Agent 运行', [
|
|
1272
1381
|
{ label: '已启用', value: detail.agentEnabled ? '是' : '否', tone: detail.agentEnabled ? 'ok' : 'warn' },
|
|
1273
1382
|
{ label: '程序', value: detail.agentProgram || '—' },
|
|
1274
1383
|
{ label: '支持 resume', value: detail.resumeSupported ? '是' : '否', tone: detail.resumeSupported ? 'ok' : 'warn' },
|
|
1275
|
-
{ label: '最近 resume', value:
|
|
1384
|
+
{ label: '最近 resume', value: lastResumeText },
|
|
1276
1385
|
{ label: '最近结果', value: detail.lastResumeOk == null ? '暂无' : (detail.lastResumeOk ? '成功' : '失败'), tone: detail.lastResumeOk == null ? 'info' : (detail.lastResumeOk ? 'ok' : 'danger') }
|
|
1277
1386
|
]);
|
|
1278
|
-
renderKeyValueCard(detailSummary, '
|
|
1279
|
-
{ label: '
|
|
1280
|
-
{ label: '
|
|
1281
|
-
{ label: '
|
|
1282
|
-
{ label: 'containerMode', value: applied.containerMode || 'default' },
|
|
1283
|
-
{ label: 'env/vol/ports', value: `${applied.envCount || 0} / ${applied.volumeCount || 0} / ${applied.portCount || 0}` }
|
|
1387
|
+
renderKeyValueCard(detailSummary, '最近活动', [
|
|
1388
|
+
{ label: '最近角色', value: latestRoleLabel },
|
|
1389
|
+
{ label: '最近时间', value: latestTimestampText },
|
|
1390
|
+
{ label: 'resume 状态', value: resumeStatusValue, tone: resumeStatusTone }
|
|
1284
1391
|
]);
|
|
1285
1392
|
}
|
|
1286
1393
|
|
|
1287
1394
|
if (configSummary) {
|
|
1288
1395
|
configSummary.innerHTML = '';
|
|
1289
|
-
renderKeyValueCard(configSummary, '
|
|
1290
|
-
{ label: '
|
|
1291
|
-
{ label: '
|
|
1292
|
-
{ label: 'containerPath', value: applied.containerPath || '—' },
|
|
1396
|
+
renderKeyValueCard(configSummary, '基础配置', [
|
|
1397
|
+
{ label: 'AGENT', value: detail.agentName || '—' },
|
|
1398
|
+
{ label: 'containerName', value: applied.containerName || detail.containerName || '—' },
|
|
1293
1399
|
{ label: 'imageName', value: applied.imageName || detail.image || '—' },
|
|
1294
1400
|
{ label: 'imageVersion', value: applied.imageVersion || '—' },
|
|
1295
1401
|
{ label: 'containerMode', value: applied.containerMode || 'default' }
|
|
1296
|
-
], { actionLabel: '打开配置', actionId: 'configSummaryOpenBtn' });
|
|
1297
|
-
renderKeyValueCard(configSummary, '命令与 Agent', [
|
|
1298
|
-
{ label: 'shellPrefix', value: applied.shellPrefix || '—' },
|
|
1299
|
-
{ label: 'shell', value: applied.shell || '—' },
|
|
1300
|
-
{ label: 'shellSuffix', value: applied.shellSuffix || '—' },
|
|
1301
|
-
{ label: '默认命令', value: applied.defaultCommand || '—' },
|
|
1302
|
-
{ label: 'Agent 模板', value: detail.agentPromptCommand || '—' },
|
|
1303
|
-
{ label: 'yolo', value: applied.yolo || '—' }
|
|
1304
1402
|
]);
|
|
1403
|
+
renderKeyValueCard(configSummary, '路径与资源', [
|
|
1404
|
+
{ label: 'hostPath', value: applied.hostPath || '—' },
|
|
1405
|
+
{ label: 'containerPath', value: applied.containerPath || '—' },
|
|
1406
|
+
{ label: 'env 数量', value: String(applied.envCount || 0) },
|
|
1407
|
+
{ label: 'volume 数量', value: String(applied.volumeCount || 0) },
|
|
1408
|
+
{ label: 'port 数量', value: String(applied.portCount || 0) }
|
|
1409
|
+
]);
|
|
1410
|
+
renderKeyValueCard(configSummary, '命令与 Agent', commandEntries);
|
|
1305
1411
|
}
|
|
1306
1412
|
|
|
1307
1413
|
if (checkSummary) {
|
|
1308
1414
|
checkSummary.innerHTML = '';
|
|
1309
|
-
renderCheckCard(checkSummary, '
|
|
1415
|
+
renderCheckCard(checkSummary, '运行检查', [
|
|
1310
1416
|
{
|
|
1311
1417
|
label: '容器状态',
|
|
1312
1418
|
value: status.label,
|
|
@@ -1314,28 +1420,32 @@
|
|
|
1314
1420
|
detail: status.tone === 'running' ? '容器处于可交互状态。' : '当前不是活跃运行态,部分功能可能受限。'
|
|
1315
1421
|
},
|
|
1316
1422
|
{
|
|
1317
|
-
label: 'Agent
|
|
1423
|
+
label: 'Agent 输入',
|
|
1318
1424
|
value: detail.agentEnabled ? '已配置' : '未配置',
|
|
1319
1425
|
tone: detail.agentEnabled ? 'ok' : 'warn',
|
|
1320
1426
|
detail: detail.agentEnabled ? '活动页可直接发送 Agent 提示词。' : '当前会话不支持 Agent 模式。'
|
|
1321
1427
|
},
|
|
1322
1428
|
{
|
|
1323
|
-
label: 'Resume
|
|
1324
|
-
value:
|
|
1325
|
-
tone:
|
|
1326
|
-
detail:
|
|
1429
|
+
label: 'Resume 健康',
|
|
1430
|
+
value: resumeStatusValue,
|
|
1431
|
+
tone: resumeStatusTone,
|
|
1432
|
+
detail: resumeStatusDetail
|
|
1327
1433
|
},
|
|
1328
1434
|
{
|
|
1329
|
-
label: '
|
|
1330
|
-
value:
|
|
1331
|
-
tone:
|
|
1332
|
-
detail:
|
|
1435
|
+
label: '镜像版本',
|
|
1436
|
+
value: imageVersionValid ? '格式正常' : '格式异常',
|
|
1437
|
+
tone: imageVersionValid ? 'ok' : 'danger',
|
|
1438
|
+
detail: applied.imageVersion
|
|
1439
|
+
? `当前值:${applied.imageVersion}。建议保持 x.y.z-后缀 格式,便于 manyoyo 的版本校验。`
|
|
1440
|
+
: '缺少 imageVersion,manyoyo 的版本校验会失效。'
|
|
1333
1441
|
},
|
|
1334
1442
|
{
|
|
1335
|
-
label: '
|
|
1336
|
-
value: applied.hostPath && applied.containerPath ? '
|
|
1443
|
+
label: '工作目录映射',
|
|
1444
|
+
value: applied.hostPath && applied.containerPath ? '已配置' : '缺失',
|
|
1337
1445
|
tone: applied.hostPath && applied.containerPath ? 'ok' : 'danger',
|
|
1338
|
-
detail:
|
|
1446
|
+
detail: applied.hostPath && applied.containerPath
|
|
1447
|
+
? '宿主目录与容器目录都已配置。'
|
|
1448
|
+
: 'hostPath / containerPath 是容器会话最关键的上下文。'
|
|
1339
1449
|
}
|
|
1340
1450
|
]);
|
|
1341
1451
|
if (detail.lastResumeError) {
|
|
@@ -1349,13 +1459,6 @@
|
|
|
1349
1459
|
]);
|
|
1350
1460
|
}
|
|
1351
1461
|
}
|
|
1352
|
-
|
|
1353
|
-
const configSummaryOpenBtn = document.getElementById('configSummaryOpenBtn');
|
|
1354
|
-
if (configSummaryOpenBtn) {
|
|
1355
|
-
configSummaryOpenBtn.addEventListener('click', function () {
|
|
1356
|
-
openConfigModal();
|
|
1357
|
-
});
|
|
1358
|
-
}
|
|
1359
1462
|
}
|
|
1360
1463
|
|
|
1361
1464
|
function syncUi() {
|
|
@@ -1366,8 +1469,9 @@
|
|
|
1366
1469
|
commandInput.value = '';
|
|
1367
1470
|
}
|
|
1368
1471
|
} else {
|
|
1369
|
-
|
|
1370
|
-
|
|
1472
|
+
const activeSession = getActiveSession();
|
|
1473
|
+
activeTitle.textContent = activeSession && activeSession.agentName ? activeSession.agentName : state.active;
|
|
1474
|
+
activeMeta.textContent = buildActiveMeta(activeSession);
|
|
1371
1475
|
}
|
|
1372
1476
|
|
|
1373
1477
|
const activityTab = state.activeTab === 'activity';
|
|
@@ -1444,7 +1548,7 @@
|
|
|
1444
1548
|
openConfigBtn.disabled = state.configLoading || state.configSaving;
|
|
1445
1549
|
}
|
|
1446
1550
|
if (configSaveBtn) {
|
|
1447
|
-
configSaveBtn.disabled =
|
|
1551
|
+
configSaveBtn.disabled = true;
|
|
1448
1552
|
}
|
|
1449
1553
|
if (configReloadBtn) {
|
|
1450
1554
|
configReloadBtn.disabled = state.configLoading || state.configSaving;
|
|
@@ -1482,6 +1586,13 @@
|
|
|
1482
1586
|
if (createModal) {
|
|
1483
1587
|
createModal.hidden = !state.createModalOpen;
|
|
1484
1588
|
}
|
|
1589
|
+
if (directoryPickerModal) {
|
|
1590
|
+
directoryPickerModal.hidden = !state.directoryPicker.open;
|
|
1591
|
+
}
|
|
1592
|
+
document.body.classList.toggle(
|
|
1593
|
+
'modal-open',
|
|
1594
|
+
state.configModalOpen || state.createModalOpen || state.directoryPicker.open
|
|
1595
|
+
);
|
|
1485
1596
|
if (!state.active) {
|
|
1486
1597
|
sendState.textContent = '未选择会话';
|
|
1487
1598
|
} else if (agentMode && !agentEnabled) {
|
|
@@ -1612,11 +1723,22 @@
|
|
|
1612
1723
|
syncUi();
|
|
1613
1724
|
try {
|
|
1614
1725
|
const config = await fetchConfigSnapshot();
|
|
1726
|
+
if (configModalTitle) {
|
|
1727
|
+
configModalTitle.textContent = '查看配置摘要 (~/.manyoyo/manyoyo.json)';
|
|
1728
|
+
}
|
|
1615
1729
|
if (configPath) {
|
|
1616
|
-
|
|
1730
|
+
const lines = [config.path || ''];
|
|
1731
|
+
if (config.notice) {
|
|
1732
|
+
lines.push(config.notice);
|
|
1733
|
+
}
|
|
1734
|
+
configPath.textContent = lines.filter(Boolean).join('\n');
|
|
1617
1735
|
}
|
|
1618
1736
|
if (configEditor) {
|
|
1619
|
-
configEditor.
|
|
1737
|
+
configEditor.readOnly = true;
|
|
1738
|
+
configEditor.value = stringifyPrettyJson({
|
|
1739
|
+
defaults: config.defaults || {},
|
|
1740
|
+
runs: config.parsed && config.parsed.runs ? config.parsed.runs : {}
|
|
1741
|
+
});
|
|
1620
1742
|
}
|
|
1621
1743
|
if (config.parseError) {
|
|
1622
1744
|
showConfigError('当前文件存在解析错误:' + config.parseError);
|
|
@@ -1637,23 +1759,7 @@
|
|
|
1637
1759
|
}
|
|
1638
1760
|
|
|
1639
1761
|
async function saveConfig() {
|
|
1640
|
-
|
|
1641
|
-
showConfigError('');
|
|
1642
|
-
syncUi();
|
|
1643
|
-
try {
|
|
1644
|
-
await api('/api/config', {
|
|
1645
|
-
method: 'PUT',
|
|
1646
|
-
body: JSON.stringify({ raw: configEditor ? configEditor.value : '' })
|
|
1647
|
-
});
|
|
1648
|
-
await fetchConfigSnapshot();
|
|
1649
|
-
showConfigError('');
|
|
1650
|
-
alert('配置已保存。后续新建会读取最新配置。');
|
|
1651
|
-
} catch (e) {
|
|
1652
|
-
showConfigError(e.message);
|
|
1653
|
-
} finally {
|
|
1654
|
-
state.configSaving = false;
|
|
1655
|
-
syncUi();
|
|
1656
|
-
}
|
|
1762
|
+
alert('Web 端已禁用明文配置编辑,请在本地 ~/.manyoyo/manyoyo.json 中维护敏感配置。');
|
|
1657
1763
|
}
|
|
1658
1764
|
|
|
1659
1765
|
async function openCreateModal() {
|
|
@@ -1683,6 +1789,7 @@
|
|
|
1683
1789
|
function closeCreateModal() {
|
|
1684
1790
|
state.createModalOpen = false;
|
|
1685
1791
|
setModalVisible(createModal, false);
|
|
1792
|
+
closeDirectoryPicker();
|
|
1686
1793
|
showCreateError('');
|
|
1687
1794
|
}
|
|
1688
1795
|
|
|
@@ -1691,57 +1798,159 @@
|
|
|
1691
1798
|
showCreateError('');
|
|
1692
1799
|
}
|
|
1693
1800
|
|
|
1694
|
-
function
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
skeleton.className = 'skeleton session';
|
|
1701
|
-
sessionList.appendChild(skeleton);
|
|
1801
|
+
function renderDirectoryPicker() {
|
|
1802
|
+
if (!directoryPickerModal) return;
|
|
1803
|
+
const picker = state.directoryPicker;
|
|
1804
|
+
setModalVisible(directoryPickerModal, picker.open);
|
|
1805
|
+
if (directoryPickerTitle) {
|
|
1806
|
+
directoryPickerTitle.textContent = picker.title || '选择目录';
|
|
1702
1807
|
}
|
|
1808
|
+
if (directoryPickerTip) {
|
|
1809
|
+
directoryPickerTip.textContent = picker.tip || '';
|
|
1810
|
+
}
|
|
1811
|
+
if (directoryPickerCurrent) {
|
|
1812
|
+
directoryPickerCurrent.textContent = picker.currentPath || '未选择目录';
|
|
1813
|
+
}
|
|
1814
|
+
showDirectoryPickerError(picker.error);
|
|
1815
|
+
if (directoryPickerUpBtn) {
|
|
1816
|
+
directoryPickerUpBtn.disabled = picker.loading || !picker.currentPath || !picker.parentPath;
|
|
1817
|
+
}
|
|
1818
|
+
if (directoryPickerSelectBtn) {
|
|
1819
|
+
directoryPickerSelectBtn.disabled = picker.loading || !picker.currentPath;
|
|
1820
|
+
}
|
|
1821
|
+
if (!directoryPickerList) {
|
|
1822
|
+
return;
|
|
1823
|
+
}
|
|
1824
|
+
directoryPickerList.innerHTML = '';
|
|
1825
|
+
if (picker.loading) {
|
|
1826
|
+
const loading = document.createElement('div');
|
|
1827
|
+
loading.className = 'empty';
|
|
1828
|
+
loading.textContent = '目录加载中...';
|
|
1829
|
+
directoryPickerList.appendChild(loading);
|
|
1830
|
+
return;
|
|
1831
|
+
}
|
|
1832
|
+
if (!picker.entries.length) {
|
|
1833
|
+
const empty = document.createElement('div');
|
|
1834
|
+
empty.className = 'empty';
|
|
1835
|
+
empty.textContent = '当前目录下没有可选子目录';
|
|
1836
|
+
directoryPickerList.appendChild(empty);
|
|
1837
|
+
return;
|
|
1838
|
+
}
|
|
1839
|
+
picker.entries.forEach(function (entry) {
|
|
1840
|
+
const btn = document.createElement('button');
|
|
1841
|
+
btn.type = 'button';
|
|
1842
|
+
btn.className = 'dir-picker-item secondary';
|
|
1843
|
+
btn.textContent = entry.name;
|
|
1844
|
+
btn.addEventListener('click', function () {
|
|
1845
|
+
loadDirectoryPicker(entry.path);
|
|
1846
|
+
});
|
|
1847
|
+
directoryPickerList.appendChild(btn);
|
|
1848
|
+
});
|
|
1703
1849
|
}
|
|
1704
1850
|
|
|
1705
|
-
function
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1851
|
+
async function loadDirectoryPicker(targetPath) {
|
|
1852
|
+
const picker = state.directoryPicker;
|
|
1853
|
+
picker.loading = true;
|
|
1854
|
+
picker.error = '';
|
|
1855
|
+
if (targetPath) {
|
|
1856
|
+
picker.currentPath = targetPath;
|
|
1857
|
+
}
|
|
1858
|
+
renderDirectoryPicker();
|
|
1859
|
+
try {
|
|
1860
|
+
const params = new URLSearchParams();
|
|
1861
|
+
params.set('path', picker.currentPath || '/');
|
|
1862
|
+
if (picker.basePath) {
|
|
1863
|
+
params.set('basePath', picker.basePath);
|
|
1864
|
+
}
|
|
1865
|
+
const data = await api('/api/fs/directories?' + params.toString());
|
|
1866
|
+
picker.currentPath = data.currentPath || picker.currentPath;
|
|
1867
|
+
picker.basePath = data.basePath || picker.basePath || '';
|
|
1868
|
+
picker.parentPath = data.parentPath || '';
|
|
1869
|
+
picker.entries = Array.isArray(data.entries) ? data.entries : [];
|
|
1870
|
+
} catch (e) {
|
|
1871
|
+
picker.error = e && e.message ? e.message : '目录加载失败';
|
|
1872
|
+
picker.entries = [];
|
|
1873
|
+
} finally {
|
|
1874
|
+
picker.loading = false;
|
|
1875
|
+
renderDirectoryPicker();
|
|
1876
|
+
}
|
|
1713
1877
|
}
|
|
1714
1878
|
|
|
1715
|
-
function
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1879
|
+
function closeDirectoryPicker() {
|
|
1880
|
+
state.directoryPicker.open = false;
|
|
1881
|
+
state.directoryPicker.loading = false;
|
|
1882
|
+
state.directoryPicker.mode = '';
|
|
1883
|
+
state.directoryPicker.title = '';
|
|
1884
|
+
state.directoryPicker.tip = '';
|
|
1885
|
+
state.directoryPicker.currentPath = '';
|
|
1886
|
+
state.directoryPicker.basePath = '';
|
|
1887
|
+
state.directoryPicker.parentPath = '';
|
|
1888
|
+
state.directoryPicker.entries = [];
|
|
1889
|
+
state.directoryPicker.error = '';
|
|
1890
|
+
renderDirectoryPicker();
|
|
1719
1891
|
}
|
|
1720
1892
|
|
|
1721
|
-
function
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
row.classList.toggle('active', state.active === session.name);
|
|
1726
|
-
row.classList.toggle('history-only', status.tone === 'history');
|
|
1727
|
-
row.classList.toggle('status-running', status.tone === 'running');
|
|
1728
|
-
row.classList.toggle('status-stopped', status.tone === 'stopped');
|
|
1729
|
-
row.classList.toggle('status-history', status.tone === 'history');
|
|
1730
|
-
row.classList.toggle('status-unknown', status.tone === 'unknown');
|
|
1731
|
-
if (row.__sessionNameNode) {
|
|
1732
|
-
row.__sessionNameNode.textContent = session.name;
|
|
1733
|
-
}
|
|
1734
|
-
if (row.__statusBadgeNode) {
|
|
1735
|
-
row.__statusBadgeNode.className = `session-status ${status.tone}`;
|
|
1736
|
-
row.__statusBadgeNode.textContent = status.label;
|
|
1893
|
+
function applyPickedDirectory() {
|
|
1894
|
+
const picker = state.directoryPicker;
|
|
1895
|
+
if (!picker.currentPath) {
|
|
1896
|
+
return;
|
|
1737
1897
|
}
|
|
1738
|
-
if (
|
|
1739
|
-
|
|
1898
|
+
if (picker.mode === 'host') {
|
|
1899
|
+
createHostPath.value = picker.currentPath;
|
|
1900
|
+
if (!(createContainerPath.value || '').trim()) {
|
|
1901
|
+
createContainerPath.value = '/workspace';
|
|
1902
|
+
}
|
|
1903
|
+
} else if (picker.mode === 'container') {
|
|
1904
|
+
const mapped = buildContainerPathFromHostSelection(
|
|
1905
|
+
picker.basePath,
|
|
1906
|
+
(createContainerPath.value || '').trim() || '/workspace',
|
|
1907
|
+
picker.currentPath
|
|
1908
|
+
);
|
|
1909
|
+
createContainerPath.value = mapped;
|
|
1910
|
+
}
|
|
1911
|
+
closeDirectoryPicker();
|
|
1912
|
+
}
|
|
1913
|
+
|
|
1914
|
+
function openDirectoryPicker(mode) {
|
|
1915
|
+
const picker = state.directoryPicker;
|
|
1916
|
+
picker.open = true;
|
|
1917
|
+
picker.loading = false;
|
|
1918
|
+
picker.error = '';
|
|
1919
|
+
picker.entries = [];
|
|
1920
|
+
picker.parentPath = '';
|
|
1921
|
+
if (mode === 'container') {
|
|
1922
|
+
const baseHostPath = (createHostPath.value || '').trim();
|
|
1923
|
+
if (!baseHostPath) {
|
|
1924
|
+
showCreateError('请先选择 hostPath,再选择 containerPath。');
|
|
1925
|
+
picker.open = false;
|
|
1926
|
+
renderDirectoryPicker();
|
|
1927
|
+
return;
|
|
1928
|
+
}
|
|
1929
|
+
picker.mode = 'container';
|
|
1930
|
+
picker.title = '选择 containerPath 对应目录';
|
|
1931
|
+
picker.tip = '从 hostPath 下选择子目录,结果会映射到容器路径。';
|
|
1932
|
+
picker.basePath = baseHostPath;
|
|
1933
|
+
picker.currentPath = baseHostPath;
|
|
1934
|
+
} else {
|
|
1935
|
+
picker.mode = 'host';
|
|
1936
|
+
picker.title = '选择 hostPath';
|
|
1937
|
+
picker.tip = '浏览宿主机目录,选中后会回填 create 表单。';
|
|
1938
|
+
picker.basePath = '';
|
|
1939
|
+
picker.currentPath = (createHostPath.value || '').trim() || '/';
|
|
1740
1940
|
}
|
|
1741
|
-
|
|
1742
|
-
|
|
1941
|
+
renderDirectoryPicker();
|
|
1942
|
+
loadDirectoryPicker(picker.currentPath);
|
|
1943
|
+
}
|
|
1944
|
+
|
|
1945
|
+
function renderSessionsLoading() {
|
|
1946
|
+
state.sessionNodeMap.clear();
|
|
1947
|
+
state.sessionRenderMode = 'loading';
|
|
1948
|
+
sessionList.innerHTML = '';
|
|
1949
|
+
for (let i = 0; i < 3; i++) {
|
|
1950
|
+
const skeleton = document.createElement('div');
|
|
1951
|
+
skeleton.className = 'skeleton session';
|
|
1952
|
+
sessionList.appendChild(skeleton);
|
|
1743
1953
|
}
|
|
1744
|
-
row.__renderKey = getSessionRenderKey(session);
|
|
1745
1954
|
}
|
|
1746
1955
|
|
|
1747
1956
|
function handleSessionItemClick(sessionName) {
|
|
@@ -1772,7 +1981,7 @@
|
|
|
1772
1981
|
}
|
|
1773
1982
|
}
|
|
1774
1983
|
}
|
|
1775
|
-
|
|
1984
|
+
renderSessions();
|
|
1776
1985
|
syncUi();
|
|
1777
1986
|
Promise.all([
|
|
1778
1987
|
loadMessagesForSession(sessionName),
|
|
@@ -1782,49 +1991,113 @@
|
|
|
1782
1991
|
});
|
|
1783
1992
|
}
|
|
1784
1993
|
|
|
1785
|
-
function
|
|
1994
|
+
async function createAgentSession(containerName) {
|
|
1995
|
+
const targetContainer = String(containerName || '').trim();
|
|
1996
|
+
if (!targetContainer) {
|
|
1997
|
+
return;
|
|
1998
|
+
}
|
|
1999
|
+
try {
|
|
2000
|
+
const data = await api('/api/sessions/' + encodeURIComponent(targetContainer) + '/agents', {
|
|
2001
|
+
method: 'POST',
|
|
2002
|
+
body: JSON.stringify({})
|
|
2003
|
+
});
|
|
2004
|
+
state.activeTab = 'activity';
|
|
2005
|
+
state.mode = 'agent';
|
|
2006
|
+
await loadSessions(data.name);
|
|
2007
|
+
if (isMobileLayout()) {
|
|
2008
|
+
closeMobileSessionPanel();
|
|
2009
|
+
}
|
|
2010
|
+
} catch (e) {
|
|
2011
|
+
alert(e.message);
|
|
2012
|
+
}
|
|
2013
|
+
}
|
|
2014
|
+
|
|
2015
|
+
function groupSessionsByDirectory(sessions) {
|
|
2016
|
+
const groups = new Map();
|
|
2017
|
+
(Array.isArray(sessions) ? sessions : []).forEach(function (session) {
|
|
2018
|
+
const directoryPath = String(session && session.hostPath ? session.hostPath : '').trim() || '未配置目录';
|
|
2019
|
+
if (!groups.has(directoryPath)) {
|
|
2020
|
+
groups.set(directoryPath, {
|
|
2021
|
+
path: directoryPath,
|
|
2022
|
+
updatedAt: session && session.updatedAt ? session.updatedAt : '',
|
|
2023
|
+
containers: new Map()
|
|
2024
|
+
});
|
|
2025
|
+
}
|
|
2026
|
+
const directoryGroup = groups.get(directoryPath);
|
|
2027
|
+
if (session && session.updatedAt && (!directoryGroup.updatedAt || new Date(session.updatedAt).getTime() > new Date(directoryGroup.updatedAt).getTime())) {
|
|
2028
|
+
directoryGroup.updatedAt = session.updatedAt;
|
|
2029
|
+
}
|
|
2030
|
+
|
|
2031
|
+
const containerName = String(session && session.containerName ? session.containerName : '');
|
|
2032
|
+
if (!directoryGroup.containers.has(containerName)) {
|
|
2033
|
+
directoryGroup.containers.set(containerName, {
|
|
2034
|
+
containerName: containerName,
|
|
2035
|
+
status: session && session.status ? session.status : 'history',
|
|
2036
|
+
image: session && session.image ? session.image : '',
|
|
2037
|
+
updatedAt: session && session.updatedAt ? session.updatedAt : '',
|
|
2038
|
+
sessions: []
|
|
2039
|
+
});
|
|
2040
|
+
}
|
|
2041
|
+
const containerGroup = directoryGroup.containers.get(containerName);
|
|
2042
|
+
containerGroup.sessions.push(session);
|
|
2043
|
+
if (session && session.updatedAt && (!containerGroup.updatedAt || new Date(session.updatedAt).getTime() > new Date(containerGroup.updatedAt).getTime())) {
|
|
2044
|
+
containerGroup.updatedAt = session.updatedAt;
|
|
2045
|
+
}
|
|
2046
|
+
});
|
|
2047
|
+
return Array.from(groups.values()).sort(function (a, b) {
|
|
2048
|
+
const timeA = a.updatedAt ? new Date(a.updatedAt).getTime() : 0;
|
|
2049
|
+
const timeB = b.updatedAt ? new Date(b.updatedAt).getTime() : 0;
|
|
2050
|
+
return timeB - timeA;
|
|
2051
|
+
});
|
|
2052
|
+
}
|
|
2053
|
+
|
|
2054
|
+
function createAgentRow(session, index) {
|
|
1786
2055
|
const status = sessionStatusInfo(session.status);
|
|
1787
2056
|
const btn = document.createElement('button');
|
|
1788
2057
|
btn.type = 'button';
|
|
1789
|
-
btn.className = '
|
|
2058
|
+
btn.className = 'agent-item';
|
|
1790
2059
|
btn.dataset.sessionName = session.name;
|
|
2060
|
+
btn.style.setProperty('--item-index', String(index));
|
|
2061
|
+
btn.classList.toggle('active', state.active === session.name);
|
|
1791
2062
|
|
|
1792
2063
|
const sessionName = document.createElement('div');
|
|
1793
|
-
sessionName.className = '
|
|
2064
|
+
sessionName.className = 'agent-name';
|
|
2065
|
+
sessionName.textContent = session.agentName || session.name;
|
|
1794
2066
|
|
|
1795
2067
|
const meta = document.createElement('div');
|
|
1796
|
-
meta.className = '
|
|
2068
|
+
meta.className = 'agent-meta';
|
|
1797
2069
|
|
|
1798
2070
|
const statusBadge = document.createElement('span');
|
|
1799
2071
|
statusBadge.className = `session-status ${status.tone}`;
|
|
2072
|
+
statusBadge.textContent = status.label;
|
|
1800
2073
|
|
|
1801
2074
|
const messageCount = document.createElement('span');
|
|
1802
2075
|
messageCount.className = 'session-count';
|
|
2076
|
+
messageCount.textContent = `${safeMessageCount(session.messageCount)} 条`;
|
|
1803
2077
|
|
|
1804
2078
|
meta.appendChild(statusBadge);
|
|
1805
2079
|
meta.appendChild(messageCount);
|
|
1806
2080
|
|
|
1807
2081
|
const time = document.createElement('div');
|
|
1808
|
-
time.className = '
|
|
2082
|
+
time.className = 'agent-time';
|
|
2083
|
+
time.textContent = formatDateTime(session.updatedAt) || '暂无更新';
|
|
1809
2084
|
|
|
1810
2085
|
btn.appendChild(sessionName);
|
|
1811
2086
|
btn.appendChild(meta);
|
|
1812
2087
|
btn.appendChild(time);
|
|
1813
|
-
btn.__sessionNameNode = sessionName;
|
|
1814
|
-
btn.__statusBadgeNode = statusBadge;
|
|
1815
|
-
btn.__messageCountNode = messageCount;
|
|
1816
|
-
btn.__timeNode = time;
|
|
1817
|
-
|
|
1818
2088
|
btn.addEventListener('click', function () {
|
|
1819
2089
|
handleSessionItemClick(btn.dataset.sessionName || '');
|
|
1820
2090
|
});
|
|
1821
|
-
|
|
1822
|
-
updateSessionRow(btn, session, index);
|
|
1823
2091
|
return btn;
|
|
1824
2092
|
}
|
|
1825
2093
|
|
|
1826
2094
|
function renderSessions() {
|
|
1827
|
-
|
|
2095
|
+
const containerCount = new Set(state.sessions.map(function (session) {
|
|
2096
|
+
return session && session.containerName ? session.containerName : '';
|
|
2097
|
+
}).filter(Boolean)).size;
|
|
2098
|
+
sessionCount.textContent = state.loadingSessions
|
|
2099
|
+
? '加载中...'
|
|
2100
|
+
: `${state.sessions.length} 个 AGENT / ${containerCount} 个容器`;
|
|
1828
2101
|
|
|
1829
2102
|
if (state.loadingSessions) {
|
|
1830
2103
|
renderSessionsLoading();
|
|
@@ -1842,49 +2115,74 @@
|
|
|
1842
2115
|
return;
|
|
1843
2116
|
}
|
|
1844
2117
|
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
state.sessionRenderMode = 'list';
|
|
1849
|
-
}
|
|
1850
|
-
|
|
1851
|
-
const nextNameSet = new Set();
|
|
1852
|
-
state.sessions.forEach(function (session, index) {
|
|
1853
|
-
nextNameSet.add(session.name);
|
|
1854
|
-
let row = state.sessionNodeMap.get(session.name);
|
|
1855
|
-
if (!row) {
|
|
1856
|
-
row = createSessionRow(session, index);
|
|
1857
|
-
state.sessionNodeMap.set(session.name, row);
|
|
1858
|
-
} else if (row.__renderKey !== getSessionRenderKey(session)) {
|
|
1859
|
-
updateSessionRow(row, session, index);
|
|
1860
|
-
} else {
|
|
1861
|
-
row.style.setProperty('--item-index', String(index));
|
|
1862
|
-
}
|
|
2118
|
+
sessionList.innerHTML = '';
|
|
2119
|
+
state.sessionNodeMap.clear();
|
|
2120
|
+
state.sessionRenderMode = 'tree';
|
|
1863
2121
|
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
sessionList.insertBefore(row, currentAtIndex || null);
|
|
1867
|
-
}
|
|
1868
|
-
});
|
|
2122
|
+
const grouped = groupSessionsByDirectory(state.sessions);
|
|
2123
|
+
let itemIndex = 0;
|
|
1869
2124
|
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
removeNames.push(existingName);
|
|
1874
|
-
}
|
|
1875
|
-
}
|
|
1876
|
-
removeNames.forEach(function (name) {
|
|
1877
|
-
const row = state.sessionNodeMap.get(name);
|
|
1878
|
-
if (row && row.parentNode === sessionList) {
|
|
1879
|
-
sessionList.removeChild(row);
|
|
1880
|
-
}
|
|
1881
|
-
state.sessionNodeMap.delete(name);
|
|
1882
|
-
});
|
|
2125
|
+
grouped.forEach(function (directoryGroup) {
|
|
2126
|
+
const group = document.createElement('section');
|
|
2127
|
+
group.className = 'workbench-group';
|
|
1883
2128
|
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
2129
|
+
const groupHead = document.createElement('div');
|
|
2130
|
+
groupHead.className = 'workbench-group-head';
|
|
2131
|
+
groupHead.innerHTML = `
|
|
2132
|
+
<div class="workbench-group-kicker">目录</div>
|
|
2133
|
+
<div class="workbench-group-title">${escapeHtml(directoryGroup.path)}</div>
|
|
2134
|
+
`;
|
|
2135
|
+
group.appendChild(groupHead);
|
|
2136
|
+
|
|
2137
|
+
const containerStack = document.createElement('div');
|
|
2138
|
+
containerStack.className = 'container-stack';
|
|
2139
|
+
|
|
2140
|
+
Array.from(directoryGroup.containers.values()).forEach(function (containerGroup) {
|
|
2141
|
+
const status = sessionStatusInfo(containerGroup.status);
|
|
2142
|
+
const containerCard = document.createElement('section');
|
|
2143
|
+
containerCard.className = 'container-card';
|
|
2144
|
+
|
|
2145
|
+
const containerHead = document.createElement('div');
|
|
2146
|
+
containerHead.className = 'container-card-head';
|
|
2147
|
+
|
|
2148
|
+
const containerInfo = document.createElement('div');
|
|
2149
|
+
containerInfo.className = 'container-card-info';
|
|
2150
|
+
containerInfo.innerHTML = `
|
|
2151
|
+
<div class="container-card-kicker">容器</div>
|
|
2152
|
+
<div class="container-card-title">${escapeHtml(containerGroup.containerName)}</div>
|
|
2153
|
+
<div class="container-card-meta">
|
|
2154
|
+
<span class="session-status ${status.tone}">${escapeHtml(status.label)}</span>
|
|
2155
|
+
<span>${escapeHtml(formatDateTime(containerGroup.updatedAt) || '暂无更新')}</span>
|
|
2156
|
+
</div>
|
|
2157
|
+
`;
|
|
2158
|
+
|
|
2159
|
+
const addAgentBtn = document.createElement('button');
|
|
2160
|
+
addAgentBtn.type = 'button';
|
|
2161
|
+
addAgentBtn.className = 'secondary add-agent-btn';
|
|
2162
|
+
addAgentBtn.textContent = '新建AGENT';
|
|
2163
|
+
addAgentBtn.addEventListener('click', function () {
|
|
2164
|
+
createAgentSession(containerGroup.containerName);
|
|
2165
|
+
});
|
|
2166
|
+
|
|
2167
|
+
containerHead.appendChild(containerInfo);
|
|
2168
|
+
containerHead.appendChild(addAgentBtn);
|
|
2169
|
+
containerCard.appendChild(containerHead);
|
|
2170
|
+
|
|
2171
|
+
const agentList = document.createElement('div');
|
|
2172
|
+
agentList.className = 'agent-list';
|
|
2173
|
+
containerGroup.sessions.forEach(function (session) {
|
|
2174
|
+
const row = createAgentRow(session, itemIndex);
|
|
2175
|
+
state.sessionNodeMap.set(session.name, row);
|
|
2176
|
+
agentList.appendChild(row);
|
|
2177
|
+
itemIndex += 1;
|
|
2178
|
+
});
|
|
2179
|
+
containerCard.appendChild(agentList);
|
|
2180
|
+
containerStack.appendChild(containerCard);
|
|
2181
|
+
});
|
|
2182
|
+
|
|
2183
|
+
group.appendChild(containerStack);
|
|
2184
|
+
sessionList.appendChild(group);
|
|
2185
|
+
});
|
|
1888
2186
|
}
|
|
1889
2187
|
|
|
1890
2188
|
function renderMessagesLoading() {
|
|
@@ -2487,6 +2785,38 @@
|
|
|
2487
2785
|
});
|
|
2488
2786
|
}
|
|
2489
2787
|
|
|
2788
|
+
if (pickHostPathBtn) {
|
|
2789
|
+
pickHostPathBtn.addEventListener('click', function () {
|
|
2790
|
+
openDirectoryPicker('host');
|
|
2791
|
+
});
|
|
2792
|
+
}
|
|
2793
|
+
|
|
2794
|
+
if (pickContainerPathBtn) {
|
|
2795
|
+
pickContainerPathBtn.addEventListener('click', function () {
|
|
2796
|
+
openDirectoryPicker('container');
|
|
2797
|
+
});
|
|
2798
|
+
}
|
|
2799
|
+
|
|
2800
|
+
if (directoryPickerCancelBtn) {
|
|
2801
|
+
directoryPickerCancelBtn.addEventListener('click', function () {
|
|
2802
|
+
closeDirectoryPicker();
|
|
2803
|
+
});
|
|
2804
|
+
}
|
|
2805
|
+
|
|
2806
|
+
if (directoryPickerUpBtn) {
|
|
2807
|
+
directoryPickerUpBtn.addEventListener('click', function () {
|
|
2808
|
+
if (state.directoryPicker.parentPath) {
|
|
2809
|
+
loadDirectoryPicker(state.directoryPicker.parentPath);
|
|
2810
|
+
}
|
|
2811
|
+
});
|
|
2812
|
+
}
|
|
2813
|
+
|
|
2814
|
+
if (directoryPickerSelectBtn) {
|
|
2815
|
+
directoryPickerSelectBtn.addEventListener('click', function () {
|
|
2816
|
+
applyPickedDirectory();
|
|
2817
|
+
});
|
|
2818
|
+
}
|
|
2819
|
+
|
|
2490
2820
|
if (createRun) {
|
|
2491
2821
|
createRun.addEventListener('change', function () {
|
|
2492
2822
|
applyCurrentRunDefaults();
|
|
@@ -2787,6 +3117,14 @@
|
|
|
2787
3117
|
});
|
|
2788
3118
|
}
|
|
2789
3119
|
|
|
3120
|
+
if (directoryPickerModal) {
|
|
3121
|
+
directoryPickerModal.addEventListener('click', function (event) {
|
|
3122
|
+
if (event.target === directoryPickerModal && !state.directoryPicker.loading) {
|
|
3123
|
+
closeDirectoryPicker();
|
|
3124
|
+
}
|
|
3125
|
+
});
|
|
3126
|
+
}
|
|
3127
|
+
|
|
2790
3128
|
window.addEventListener('keydown', function (event) {
|
|
2791
3129
|
if (event.key === 'Escape' && state.configModalOpen) {
|
|
2792
3130
|
closeConfigModal();
|
|
@@ -2796,6 +3134,9 @@
|
|
|
2796
3134
|
closeCreateModal();
|
|
2797
3135
|
syncUi();
|
|
2798
3136
|
}
|
|
3137
|
+
if (event.key === 'Escape' && state.directoryPicker.open) {
|
|
3138
|
+
closeDirectoryPicker();
|
|
3139
|
+
}
|
|
2799
3140
|
if (event.key === 'Escape' && state.mobileSidebarOpen) {
|
|
2800
3141
|
closeMobileSessionPanel();
|
|
2801
3142
|
}
|
|
@@ -2842,7 +3183,9 @@
|
|
|
2842
3183
|
removeBtn.addEventListener('click', async function () {
|
|
2843
3184
|
if (!state.active) return;
|
|
2844
3185
|
closeMobileActionsMenu();
|
|
2845
|
-
const
|
|
3186
|
+
const activeSession = getActiveSession();
|
|
3187
|
+
const targetContainer = activeSession && activeSession.containerName ? activeSession.containerName : state.active;
|
|
3188
|
+
const yes = confirm('确认删除容器 ' + targetContainer + ' ?');
|
|
2846
3189
|
if (!yes) return;
|
|
2847
3190
|
try {
|
|
2848
3191
|
const current = state.active;
|
|
@@ -2861,7 +3204,9 @@
|
|
|
2861
3204
|
removeAllBtn.addEventListener('click', async function () {
|
|
2862
3205
|
if (!state.active) return;
|
|
2863
3206
|
closeMobileActionsMenu();
|
|
2864
|
-
const
|
|
3207
|
+
const activeSession = getActiveSession();
|
|
3208
|
+
const targetAgent = activeSession && activeSession.agentName ? activeSession.agentName : state.active;
|
|
3209
|
+
const yes = confirm('确认删除对话 ' + targetAgent + ' ?');
|
|
2865
3210
|
if (!yes) return;
|
|
2866
3211
|
try {
|
|
2867
3212
|
const current = state.active;
|