codexmate 0.0.13 → 0.0.14
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.en.md +429 -0
- package/README.md +231 -215
- package/cli.js +2450 -137
- package/doc/CHANGELOG.md +10 -3
- package/doc/CHANGELOG.zh-CN.md +7 -0
- package/lib/cli-utils.js +16 -0
- package/lib/workflow-engine.js +340 -0
- package/package.json +11 -4
- package/web-ui/app.js +515 -5
- package/web-ui/index.html +242 -19
- package/web-ui/logic.mjs +147 -1
- package/web-ui/styles.css +648 -10
- package/README.zh-CN.md +0 -419
package/web-ui/app.js
CHANGED
|
@@ -7,7 +7,12 @@
|
|
|
7
7
|
formatLatency,
|
|
8
8
|
buildSpeedTestIssue,
|
|
9
9
|
isSessionQueryEnabled,
|
|
10
|
-
buildSessionListParams
|
|
10
|
+
buildSessionListParams,
|
|
11
|
+
normalizeSessionSource,
|
|
12
|
+
normalizeSessionPathFilter,
|
|
13
|
+
buildSessionFilterCacheState,
|
|
14
|
+
buildSessionTimelineNodes,
|
|
15
|
+
normalizeSessionMessageRole
|
|
11
16
|
} from './logic.mjs';
|
|
12
17
|
|
|
13
18
|
document.addEventListener('DOMContentLoaded', () => {
|
|
@@ -81,6 +86,7 @@
|
|
|
81
86
|
showOpenclawConfigModal: false,
|
|
82
87
|
showConfigTemplateModal: false,
|
|
83
88
|
showAgentsModal: false,
|
|
89
|
+
showSkillsModal: false,
|
|
84
90
|
showInstallModal: false,
|
|
85
91
|
configTemplateContent: '',
|
|
86
92
|
configTemplateApplying: false,
|
|
@@ -94,6 +100,17 @@
|
|
|
94
100
|
agentsContext: 'codex',
|
|
95
101
|
agentsModalTitle: 'AGENTS.md 编辑器',
|
|
96
102
|
agentsModalHint: '保存后会写入目标 AGENTS.md(与 config.toml 同级)。',
|
|
103
|
+
skillsRootPath: '',
|
|
104
|
+
skillsList: [],
|
|
105
|
+
skillsSelectedNames: [],
|
|
106
|
+
skillsLoading: false,
|
|
107
|
+
skillsDeleting: false,
|
|
108
|
+
skillsKeyword: '',
|
|
109
|
+
skillsStatusFilter: 'all',
|
|
110
|
+
skillsImportList: [],
|
|
111
|
+
skillsImportSelectedKeys: [],
|
|
112
|
+
skillsScanningImports: false,
|
|
113
|
+
skillsImporting: false,
|
|
97
114
|
sessionsList: [],
|
|
98
115
|
sessionsLoading: false,
|
|
99
116
|
sessionFilterSource: 'all',
|
|
@@ -124,6 +141,13 @@
|
|
|
124
141
|
activeSessionDetailClipped: false,
|
|
125
142
|
sessionDetailLoading: false,
|
|
126
143
|
sessionDetailRequestSeq: 0,
|
|
144
|
+
sessionTimelineActiveKey: '',
|
|
145
|
+
sessionTimelineRafId: 0,
|
|
146
|
+
sessionMessageRefMap: Object.create(null),
|
|
147
|
+
sessionPreviewScrollEl: null,
|
|
148
|
+
sessionPreviewContainerEl: null,
|
|
149
|
+
sessionPreviewHeaderEl: null,
|
|
150
|
+
sessionPreviewHeaderResizeObserver: null,
|
|
127
151
|
sessionStandalone: false,
|
|
128
152
|
sessionStandaloneError: '',
|
|
129
153
|
sessionStandaloneText: '',
|
|
@@ -269,6 +293,8 @@
|
|
|
269
293
|
} else if (savedSessionYolo === '1' || savedSessionYolo === 'true') {
|
|
270
294
|
this.sessionResumeWithYolo = true;
|
|
271
295
|
}
|
|
296
|
+
this.restoreSessionFilterCache();
|
|
297
|
+
window.addEventListener('resize', this.onWindowResize);
|
|
272
298
|
const savedConfigs = localStorage.getItem('claudeConfigs');
|
|
273
299
|
if (savedConfigs) {
|
|
274
300
|
try {
|
|
@@ -304,11 +330,31 @@
|
|
|
304
330
|
}
|
|
305
331
|
this.loadAll();
|
|
306
332
|
},
|
|
333
|
+
beforeUnmount() {
|
|
334
|
+
this.cancelSessionTimelineSync();
|
|
335
|
+
this.disconnectSessionPreviewHeaderResizeObserver();
|
|
336
|
+
window.removeEventListener('resize', this.onWindowResize);
|
|
337
|
+
this.sessionPreviewScrollEl = null;
|
|
338
|
+
this.sessionPreviewContainerEl = null;
|
|
339
|
+
this.sessionPreviewHeaderEl = null;
|
|
340
|
+
this.sessionMessageRefMap = Object.create(null);
|
|
341
|
+
},
|
|
307
342
|
|
|
308
343
|
computed: {
|
|
309
344
|
isSessionQueryEnabled() {
|
|
310
345
|
return isSessionQueryEnabled(this.sessionFilterSource);
|
|
311
346
|
},
|
|
347
|
+
sessionTimelineNodes() {
|
|
348
|
+
return buildSessionTimelineNodes(this.activeSessionMessages, {
|
|
349
|
+
getKey: (message, index) => this.getRecordRenderKey(message, index)
|
|
350
|
+
});
|
|
351
|
+
},
|
|
352
|
+
sessionTimelineActiveTitle() {
|
|
353
|
+
if (!this.sessionTimelineActiveKey) return '';
|
|
354
|
+
const nodes = Array.isArray(this.sessionTimelineNodes) ? this.sessionTimelineNodes : [];
|
|
355
|
+
const matched = nodes.find(node => node.key === this.sessionTimelineActiveKey);
|
|
356
|
+
return matched ? matched.title : '';
|
|
357
|
+
},
|
|
312
358
|
sessionQueryPlaceholder() {
|
|
313
359
|
if (this.isSessionQueryEnabled) {
|
|
314
360
|
return '关键词检索(支持 Codex/Claude,例:claude code)';
|
|
@@ -354,6 +400,84 @@
|
|
|
354
400
|
installRegistryPreview() {
|
|
355
401
|
return this.resolveInstallRegistryUrl(this.installRegistryPreset, this.installRegistryCustom);
|
|
356
402
|
},
|
|
403
|
+
filteredSkillsList() {
|
|
404
|
+
const list = Array.isArray(this.skillsList) ? this.skillsList : [];
|
|
405
|
+
const keyword = typeof this.skillsKeyword === 'string' ? this.skillsKeyword.trim().toLowerCase() : '';
|
|
406
|
+
const status = typeof this.skillsStatusFilter === 'string' ? this.skillsStatusFilter : 'all';
|
|
407
|
+
return list.filter((item) => {
|
|
408
|
+
const safe = item && typeof item === 'object' ? item : {};
|
|
409
|
+
const hasSkillFile = !!safe.hasSkillFile;
|
|
410
|
+
if (status === 'with-skill-file' && !hasSkillFile) return false;
|
|
411
|
+
if (status === 'missing-skill-file' && hasSkillFile) return false;
|
|
412
|
+
if (!keyword) return true;
|
|
413
|
+
const fields = [
|
|
414
|
+
safe.name,
|
|
415
|
+
safe.displayName,
|
|
416
|
+
safe.description,
|
|
417
|
+
safe.path
|
|
418
|
+
];
|
|
419
|
+
return fields.some((value) => typeof value === 'string' && value.toLowerCase().includes(keyword));
|
|
420
|
+
});
|
|
421
|
+
},
|
|
422
|
+
skillsSelectableNames() {
|
|
423
|
+
const list = Array.isArray(this.filteredSkillsList) ? this.filteredSkillsList : [];
|
|
424
|
+
return list
|
|
425
|
+
.map((item) => (item && typeof item.name === 'string' ? item.name.trim() : ''))
|
|
426
|
+
.filter(Boolean);
|
|
427
|
+
},
|
|
428
|
+
skillsConfiguredCount() {
|
|
429
|
+
const list = Array.isArray(this.skillsList) ? this.skillsList : [];
|
|
430
|
+
return list.filter((item) => !!(item && item.hasSkillFile)).length;
|
|
431
|
+
},
|
|
432
|
+
skillsMissingSkillFileCount() {
|
|
433
|
+
const list = Array.isArray(this.skillsList) ? this.skillsList : [];
|
|
434
|
+
return list.filter((item) => !(item && item.hasSkillFile)).length;
|
|
435
|
+
},
|
|
436
|
+
skillsFilterDirty() {
|
|
437
|
+
const keyword = typeof this.skillsKeyword === 'string' ? this.skillsKeyword.trim() : '';
|
|
438
|
+
const status = typeof this.skillsStatusFilter === 'string' ? this.skillsStatusFilter : 'all';
|
|
439
|
+
return keyword.length > 0 || status !== 'all';
|
|
440
|
+
},
|
|
441
|
+
skillsSelectedCount() {
|
|
442
|
+
const selected = Array.isArray(this.skillsSelectedNames) ? this.skillsSelectedNames : [];
|
|
443
|
+
return Array.from(new Set(selected.map((item) => String(item || '').trim()).filter(Boolean))).length;
|
|
444
|
+
},
|
|
445
|
+
skillsVisibleSelectedCount() {
|
|
446
|
+
const selectable = this.skillsSelectableNames;
|
|
447
|
+
const selectedSet = new Set(Array.isArray(this.skillsSelectedNames) ? this.skillsSelectedNames : []);
|
|
448
|
+
return selectable.filter((name) => selectedSet.has(name)).length;
|
|
449
|
+
},
|
|
450
|
+
skillsAllSelected() {
|
|
451
|
+
const selectable = this.skillsSelectableNames;
|
|
452
|
+
if (!selectable.length) return false;
|
|
453
|
+
const selectedSet = new Set(Array.isArray(this.skillsSelectedNames) ? this.skillsSelectedNames : []);
|
|
454
|
+
return selectable.every((name) => selectedSet.has(name));
|
|
455
|
+
},
|
|
456
|
+
skillsImportSelectableKeys() {
|
|
457
|
+
const list = Array.isArray(this.skillsImportList) ? this.skillsImportList : [];
|
|
458
|
+
return list
|
|
459
|
+
.map((item) => this.buildSkillImportKey(item))
|
|
460
|
+
.filter(Boolean);
|
|
461
|
+
},
|
|
462
|
+
skillsImportSelectedCount() {
|
|
463
|
+
const selectable = this.skillsImportSelectableKeys;
|
|
464
|
+
const selectedSet = new Set(Array.isArray(this.skillsImportSelectedKeys) ? this.skillsImportSelectedKeys : []);
|
|
465
|
+
return selectable.filter((key) => selectedSet.has(key)).length;
|
|
466
|
+
},
|
|
467
|
+
skillsImportAllSelected() {
|
|
468
|
+
const selectable = this.skillsImportSelectableKeys;
|
|
469
|
+
if (!selectable.length) return false;
|
|
470
|
+
const selectedSet = new Set(Array.isArray(this.skillsImportSelectedKeys) ? this.skillsImportSelectedKeys : []);
|
|
471
|
+
return selectable.every((key) => selectedSet.has(key));
|
|
472
|
+
},
|
|
473
|
+
skillsImportConfiguredCount() {
|
|
474
|
+
const list = Array.isArray(this.skillsImportList) ? this.skillsImportList : [];
|
|
475
|
+
return list.filter((item) => !!(item && item.hasSkillFile)).length;
|
|
476
|
+
},
|
|
477
|
+
skillsImportMissingSkillFileCount() {
|
|
478
|
+
const list = Array.isArray(this.skillsImportList) ? this.skillsImportList : [];
|
|
479
|
+
return list.filter((item) => !(item && item.hasSkillFile)).length;
|
|
480
|
+
},
|
|
357
481
|
inspectorMainTabLabel() {
|
|
358
482
|
if (this.mainTab === 'config') return '配置中心';
|
|
359
483
|
if (this.mainTab === 'sessions') return '会话浏览';
|
|
@@ -418,6 +542,7 @@
|
|
|
418
542
|
if (this.codexModelsLoading || this.claudeModelsLoading) tasks.push('模型加载');
|
|
419
543
|
if (this.codexApplying || this.configTemplateApplying || this.openclawApplying) tasks.push('配置应用');
|
|
420
544
|
if (this.agentsSaving) tasks.push('AGENTS 保存');
|
|
545
|
+
if (this.skillsLoading || this.skillsDeleting || this.skillsScanningImports || this.skillsImporting) tasks.push('Skills 管理');
|
|
421
546
|
if (this.proxySaving || this.proxyApplying || this.proxyStarting || this.proxyStopping) tasks.push('代理更新');
|
|
422
547
|
return tasks.length ? tasks.join(' / ') : '空闲';
|
|
423
548
|
},
|
|
@@ -799,6 +924,9 @@
|
|
|
799
924
|
this.activeSessionMessages = [];
|
|
800
925
|
this.activeSessionDetailError = '';
|
|
801
926
|
this.activeSessionDetailClipped = false;
|
|
927
|
+
this.cancelSessionTimelineSync();
|
|
928
|
+
this.sessionTimelineActiveKey = '';
|
|
929
|
+
this.sessionMessageRefMap = Object.create(null);
|
|
802
930
|
this.sessionStandaloneError = '';
|
|
803
931
|
this.sessionStandaloneText = '';
|
|
804
932
|
this.sessionStandaloneTitle = this.activeSession.title || '会话';
|
|
@@ -1167,8 +1295,7 @@
|
|
|
1167
1295
|
},
|
|
1168
1296
|
|
|
1169
1297
|
normalizeSessionPathValue(value) {
|
|
1170
|
-
|
|
1171
|
-
return value.trim();
|
|
1298
|
+
return normalizeSessionPathFilter(value);
|
|
1172
1299
|
},
|
|
1173
1300
|
|
|
1174
1301
|
mergeSessionPathOptions(baseList = [], incomingList = []) {
|
|
@@ -1277,13 +1404,32 @@
|
|
|
1277
1404
|
const value = this.sessionResumeWithYolo ? '1' : '0';
|
|
1278
1405
|
localStorage.setItem('codexmateSessionResumeYolo', value);
|
|
1279
1406
|
},
|
|
1407
|
+
restoreSessionFilterCache() {
|
|
1408
|
+
const sourceCache = localStorage.getItem('codexmateSessionFilterSource');
|
|
1409
|
+
const pathCache = localStorage.getItem('codexmateSessionPathFilter');
|
|
1410
|
+
const cached = buildSessionFilterCacheState(sourceCache, pathCache);
|
|
1411
|
+
this.sessionFilterSource = cached.source;
|
|
1412
|
+
this.sessionPathFilter = cached.pathFilter;
|
|
1413
|
+
this.refreshSessionPathOptions(this.sessionFilterSource);
|
|
1414
|
+
},
|
|
1415
|
+
persistSessionFilterCache() {
|
|
1416
|
+
const cached = buildSessionFilterCacheState(this.sessionFilterSource, this.sessionPathFilter);
|
|
1417
|
+
localStorage.setItem('codexmateSessionFilterSource', cached.source);
|
|
1418
|
+
if (cached.pathFilter) {
|
|
1419
|
+
localStorage.setItem('codexmateSessionPathFilter', cached.pathFilter);
|
|
1420
|
+
} else {
|
|
1421
|
+
localStorage.removeItem('codexmateSessionPathFilter');
|
|
1422
|
+
}
|
|
1423
|
+
},
|
|
1280
1424
|
|
|
1281
1425
|
async onSessionSourceChange() {
|
|
1282
1426
|
this.refreshSessionPathOptions(this.sessionFilterSource);
|
|
1427
|
+
this.persistSessionFilterCache();
|
|
1283
1428
|
await this.loadSessions();
|
|
1284
1429
|
},
|
|
1285
1430
|
|
|
1286
1431
|
async onSessionPathFilterChange() {
|
|
1432
|
+
this.persistSessionFilterCache();
|
|
1287
1433
|
await this.loadSessions();
|
|
1288
1434
|
},
|
|
1289
1435
|
|
|
@@ -1297,8 +1443,156 @@
|
|
|
1297
1443
|
this.sessionQuery = '';
|
|
1298
1444
|
this.sessionRoleFilter = 'all';
|
|
1299
1445
|
this.sessionTimePreset = 'all';
|
|
1446
|
+
this.persistSessionFilterCache();
|
|
1300
1447
|
await this.onSessionSourceChange();
|
|
1301
1448
|
},
|
|
1449
|
+
setSessionPreviewContainerRef(el) {
|
|
1450
|
+
this.sessionPreviewContainerEl = el || null;
|
|
1451
|
+
this.updateSessionTimelineOffset();
|
|
1452
|
+
},
|
|
1453
|
+
disconnectSessionPreviewHeaderResizeObserver() {
|
|
1454
|
+
if (!this.sessionPreviewHeaderResizeObserver) return;
|
|
1455
|
+
this.sessionPreviewHeaderResizeObserver.disconnect();
|
|
1456
|
+
this.sessionPreviewHeaderResizeObserver = null;
|
|
1457
|
+
},
|
|
1458
|
+
observeSessionPreviewHeaderResize() {
|
|
1459
|
+
this.disconnectSessionPreviewHeaderResizeObserver();
|
|
1460
|
+
if (!this.sessionPreviewHeaderEl || typeof ResizeObserver !== 'function') return;
|
|
1461
|
+
this.sessionPreviewHeaderResizeObserver = new ResizeObserver(() => {
|
|
1462
|
+
this.updateSessionTimelineOffset();
|
|
1463
|
+
});
|
|
1464
|
+
this.sessionPreviewHeaderResizeObserver.observe(this.sessionPreviewHeaderEl);
|
|
1465
|
+
},
|
|
1466
|
+
setSessionPreviewHeaderRef(el) {
|
|
1467
|
+
this.disconnectSessionPreviewHeaderResizeObserver();
|
|
1468
|
+
this.sessionPreviewHeaderEl = el || null;
|
|
1469
|
+
this.observeSessionPreviewHeaderResize();
|
|
1470
|
+
this.updateSessionTimelineOffset();
|
|
1471
|
+
},
|
|
1472
|
+
setSessionPreviewScrollRef(el) {
|
|
1473
|
+
this.sessionPreviewScrollEl = el || null;
|
|
1474
|
+
if (this.sessionPreviewScrollEl) {
|
|
1475
|
+
this.scheduleSessionTimelineSync();
|
|
1476
|
+
} else {
|
|
1477
|
+
this.cancelSessionTimelineSync();
|
|
1478
|
+
}
|
|
1479
|
+
this.updateSessionTimelineOffset();
|
|
1480
|
+
},
|
|
1481
|
+
updateSessionTimelineOffset() {
|
|
1482
|
+
const container = this.sessionPreviewContainerEl || this.$refs.sessionPreviewContainer;
|
|
1483
|
+
if (!container || !container.style) return;
|
|
1484
|
+
const header = this.sessionPreviewHeaderEl
|
|
1485
|
+
|| (this.sessionPreviewScrollEl ? this.sessionPreviewScrollEl.querySelector('.session-preview-header') : null)
|
|
1486
|
+
|| container.querySelector('.session-preview-header');
|
|
1487
|
+
const headerHeight = header ? Math.ceil(header.getBoundingClientRect().height) : 0;
|
|
1488
|
+
const offset = headerHeight > 0 ? (headerHeight + 12) : 72;
|
|
1489
|
+
container.style.setProperty('--session-preview-header-offset', `${offset}px`);
|
|
1490
|
+
},
|
|
1491
|
+
bindSessionMessageRef(messageKey, el) {
|
|
1492
|
+
if (!messageKey) return;
|
|
1493
|
+
if (el) {
|
|
1494
|
+
this.sessionMessageRefMap[messageKey] = el;
|
|
1495
|
+
} else {
|
|
1496
|
+
delete this.sessionMessageRefMap[messageKey];
|
|
1497
|
+
}
|
|
1498
|
+
},
|
|
1499
|
+
cancelSessionTimelineSync() {
|
|
1500
|
+
if (!this.sessionTimelineRafId) return;
|
|
1501
|
+
if (typeof cancelAnimationFrame === 'function') {
|
|
1502
|
+
cancelAnimationFrame(this.sessionTimelineRafId);
|
|
1503
|
+
}
|
|
1504
|
+
this.sessionTimelineRafId = 0;
|
|
1505
|
+
},
|
|
1506
|
+
scheduleSessionTimelineSync() {
|
|
1507
|
+
if (this.sessionTimelineRafId) return;
|
|
1508
|
+
if (typeof requestAnimationFrame === 'function') {
|
|
1509
|
+
this.sessionTimelineRafId = requestAnimationFrame(() => {
|
|
1510
|
+
this.sessionTimelineRafId = 0;
|
|
1511
|
+
this.syncSessionTimelineActiveFromScroll();
|
|
1512
|
+
});
|
|
1513
|
+
return;
|
|
1514
|
+
}
|
|
1515
|
+
this.syncSessionTimelineActiveFromScroll();
|
|
1516
|
+
},
|
|
1517
|
+
onSessionPreviewScroll() {
|
|
1518
|
+
this.scheduleSessionTimelineSync();
|
|
1519
|
+
},
|
|
1520
|
+
onWindowResize() {
|
|
1521
|
+
this.updateSessionTimelineOffset();
|
|
1522
|
+
this.scheduleSessionTimelineSync();
|
|
1523
|
+
},
|
|
1524
|
+
syncSessionTimelineActiveFromScroll() {
|
|
1525
|
+
const nodes = Array.isArray(this.sessionTimelineNodes) ? this.sessionTimelineNodes : [];
|
|
1526
|
+
if (!nodes.length) {
|
|
1527
|
+
this.sessionTimelineActiveKey = '';
|
|
1528
|
+
return;
|
|
1529
|
+
}
|
|
1530
|
+
const scrollEl = this.sessionPreviewScrollEl || this.$refs.sessionPreviewScroll;
|
|
1531
|
+
if (!scrollEl) {
|
|
1532
|
+
this.sessionTimelineActiveKey = nodes[0].key;
|
|
1533
|
+
return;
|
|
1534
|
+
}
|
|
1535
|
+
const scrollRect = scrollEl.getBoundingClientRect();
|
|
1536
|
+
const headerEl = scrollEl.querySelector('.session-preview-header');
|
|
1537
|
+
const headerHeight = headerEl ? headerEl.getBoundingClientRect().height : 0;
|
|
1538
|
+
const anchorLine = scrollRect.top + headerHeight + 8;
|
|
1539
|
+
let activeKey = nodes[0].key;
|
|
1540
|
+
for (const node of nodes) {
|
|
1541
|
+
const messageEl = this.sessionMessageRefMap[node.key];
|
|
1542
|
+
if (!messageEl) continue;
|
|
1543
|
+
const messageRect = messageEl.getBoundingClientRect();
|
|
1544
|
+
if (messageRect.top <= anchorLine) {
|
|
1545
|
+
activeKey = node.key;
|
|
1546
|
+
continue;
|
|
1547
|
+
}
|
|
1548
|
+
break;
|
|
1549
|
+
}
|
|
1550
|
+
this.sessionTimelineActiveKey = activeKey;
|
|
1551
|
+
},
|
|
1552
|
+
jumpToSessionTimelineNode(messageKey) {
|
|
1553
|
+
if (!messageKey) return;
|
|
1554
|
+
const scrollEl = this.sessionPreviewScrollEl || this.$refs.sessionPreviewScroll;
|
|
1555
|
+
if (!scrollEl) return;
|
|
1556
|
+
const messageEl = this.sessionMessageRefMap[messageKey];
|
|
1557
|
+
if (!messageEl) return;
|
|
1558
|
+
const headerEl = scrollEl.querySelector('.session-preview-header');
|
|
1559
|
+
const stickyOffset = headerEl ? (headerEl.offsetHeight + 8) : 8;
|
|
1560
|
+
const scrollRect = scrollEl.getBoundingClientRect();
|
|
1561
|
+
const messageRect = messageEl.getBoundingClientRect();
|
|
1562
|
+
const targetScrollTop = scrollEl.scrollTop + (messageRect.top - scrollRect.top) - stickyOffset;
|
|
1563
|
+
this.sessionTimelineActiveKey = messageKey;
|
|
1564
|
+
if (typeof scrollEl.scrollTo === 'function') {
|
|
1565
|
+
scrollEl.scrollTo({
|
|
1566
|
+
top: Math.max(0, targetScrollTop),
|
|
1567
|
+
behavior: 'smooth'
|
|
1568
|
+
});
|
|
1569
|
+
} else {
|
|
1570
|
+
messageEl.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
|
1571
|
+
}
|
|
1572
|
+
},
|
|
1573
|
+
|
|
1574
|
+
normalizeSessionMessage(message) {
|
|
1575
|
+
const fallback = {
|
|
1576
|
+
role: 'assistant',
|
|
1577
|
+
normalizedRole: 'assistant',
|
|
1578
|
+
roleLabel: 'Assistant',
|
|
1579
|
+
text: typeof message === 'string' ? message : '',
|
|
1580
|
+
timestamp: ''
|
|
1581
|
+
};
|
|
1582
|
+
const safeMessage = message && typeof message === 'object' ? message : fallback;
|
|
1583
|
+
const normalizedRole = normalizeSessionMessageRole(
|
|
1584
|
+
safeMessage.normalizedRole || safeMessage.role
|
|
1585
|
+
);
|
|
1586
|
+
const roleLabel = normalizedRole === 'user'
|
|
1587
|
+
? 'User'
|
|
1588
|
+
: (normalizedRole === 'system' ? 'System' : 'Assistant');
|
|
1589
|
+
return {
|
|
1590
|
+
...safeMessage,
|
|
1591
|
+
role: normalizedRole,
|
|
1592
|
+
normalizedRole,
|
|
1593
|
+
roleLabel
|
|
1594
|
+
};
|
|
1595
|
+
},
|
|
1302
1596
|
|
|
1303
1597
|
getRecordKey(message) {
|
|
1304
1598
|
if (!message || !Number.isInteger(message.recordLineIndex) || message.recordLineIndex < 0) {
|
|
@@ -1347,6 +1641,9 @@
|
|
|
1347
1641
|
this.activeSession = null;
|
|
1348
1642
|
this.activeSessionMessages = [];
|
|
1349
1643
|
this.activeSessionDetailClipped = false;
|
|
1644
|
+
this.cancelSessionTimelineSync();
|
|
1645
|
+
this.sessionTimelineActiveKey = '';
|
|
1646
|
+
this.sessionMessageRefMap = Object.create(null);
|
|
1350
1647
|
} else {
|
|
1351
1648
|
this.sessionsList = Array.isArray(res.sessions) ? res.sessions : [];
|
|
1352
1649
|
this.syncSessionPathOptionsForSource(
|
|
@@ -1358,10 +1655,19 @@
|
|
|
1358
1655
|
this.activeSession = null;
|
|
1359
1656
|
this.activeSessionMessages = [];
|
|
1360
1657
|
this.activeSessionDetailClipped = false;
|
|
1658
|
+
this.cancelSessionTimelineSync();
|
|
1659
|
+
this.sessionTimelineActiveKey = '';
|
|
1660
|
+
this.sessionMessageRefMap = Object.create(null);
|
|
1361
1661
|
} else {
|
|
1362
1662
|
const oldKey = this.activeSession ? this.getSessionExportKey(this.activeSession) : '';
|
|
1363
1663
|
const matched = this.sessionsList.find(item => this.getSessionExportKey(item) === oldKey);
|
|
1364
1664
|
this.activeSession = matched || this.sessionsList[0];
|
|
1665
|
+
this.activeSessionMessages = [];
|
|
1666
|
+
this.activeSessionDetailError = '';
|
|
1667
|
+
this.activeSessionDetailClipped = false;
|
|
1668
|
+
this.cancelSessionTimelineSync();
|
|
1669
|
+
this.sessionTimelineActiveKey = '';
|
|
1670
|
+
this.sessionMessageRefMap = Object.create(null);
|
|
1365
1671
|
await this.loadActiveSessionDetail();
|
|
1366
1672
|
}
|
|
1367
1673
|
void this.loadSessionPathOptions({ source: this.sessionFilterSource });
|
|
@@ -1371,6 +1677,9 @@
|
|
|
1371
1677
|
this.activeSession = null;
|
|
1372
1678
|
this.activeSessionMessages = [];
|
|
1373
1679
|
this.activeSessionDetailClipped = false;
|
|
1680
|
+
this.cancelSessionTimelineSync();
|
|
1681
|
+
this.sessionTimelineActiveKey = '';
|
|
1682
|
+
this.sessionMessageRefMap = Object.create(null);
|
|
1374
1683
|
this.showMessage('加载会话失败', 'error');
|
|
1375
1684
|
} finally {
|
|
1376
1685
|
this.sessionsLoading = false;
|
|
@@ -1384,6 +1693,9 @@
|
|
|
1384
1693
|
this.activeSessionMessages = [];
|
|
1385
1694
|
this.activeSessionDetailError = '';
|
|
1386
1695
|
this.activeSessionDetailClipped = false;
|
|
1696
|
+
this.cancelSessionTimelineSync();
|
|
1697
|
+
this.sessionTimelineActiveKey = '';
|
|
1698
|
+
this.sessionMessageRefMap = Object.create(null);
|
|
1387
1699
|
await this.loadActiveSessionDetail();
|
|
1388
1700
|
},
|
|
1389
1701
|
|
|
@@ -1437,6 +1749,9 @@
|
|
|
1437
1749
|
this.activeSessionMessages = [];
|
|
1438
1750
|
this.activeSessionDetailError = '';
|
|
1439
1751
|
this.activeSessionDetailClipped = false;
|
|
1752
|
+
this.cancelSessionTimelineSync();
|
|
1753
|
+
this.sessionTimelineActiveKey = '';
|
|
1754
|
+
this.sessionMessageRefMap = Object.create(null);
|
|
1440
1755
|
return;
|
|
1441
1756
|
}
|
|
1442
1757
|
|
|
@@ -1459,10 +1774,14 @@
|
|
|
1459
1774
|
this.activeSessionMessages = [];
|
|
1460
1775
|
this.activeSessionDetailClipped = false;
|
|
1461
1776
|
this.activeSessionDetailError = res.error;
|
|
1777
|
+
this.cancelSessionTimelineSync();
|
|
1778
|
+
this.sessionTimelineActiveKey = '';
|
|
1779
|
+
this.sessionMessageRefMap = Object.create(null);
|
|
1462
1780
|
return;
|
|
1463
1781
|
}
|
|
1464
1782
|
|
|
1465
|
-
|
|
1783
|
+
const rawMessages = Array.isArray(res.messages) ? res.messages : [];
|
|
1784
|
+
this.activeSessionMessages = rawMessages.map((message) => this.normalizeSessionMessage(message));
|
|
1466
1785
|
this.activeSessionDetailClipped = !!res.clipped;
|
|
1467
1786
|
if (this.activeSession) {
|
|
1468
1787
|
if (res.sourceLabel) {
|
|
@@ -1487,6 +1806,10 @@
|
|
|
1487
1806
|
if (Number.isFinite(res.totalMessages)) {
|
|
1488
1807
|
this.syncActiveSessionMessageCount(res.totalMessages);
|
|
1489
1808
|
}
|
|
1809
|
+
this.$nextTick(() => {
|
|
1810
|
+
this.updateSessionTimelineOffset();
|
|
1811
|
+
this.scheduleSessionTimelineSync();
|
|
1812
|
+
});
|
|
1490
1813
|
} catch (e) {
|
|
1491
1814
|
if (requestSeq !== this.sessionDetailRequestSeq) {
|
|
1492
1815
|
return;
|
|
@@ -1494,6 +1817,9 @@
|
|
|
1494
1817
|
this.activeSessionMessages = [];
|
|
1495
1818
|
this.activeSessionDetailClipped = false;
|
|
1496
1819
|
this.activeSessionDetailError = '加载会话内容失败: ' + e.message;
|
|
1820
|
+
this.cancelSessionTimelineSync();
|
|
1821
|
+
this.sessionTimelineActiveKey = '';
|
|
1822
|
+
this.sessionMessageRefMap = Object.create(null);
|
|
1497
1823
|
} finally {
|
|
1498
1824
|
if (requestSeq === this.sessionDetailRequestSeq) {
|
|
1499
1825
|
this.sessionDetailLoading = false;
|
|
@@ -1754,6 +2080,191 @@
|
|
|
1754
2080
|
}
|
|
1755
2081
|
},
|
|
1756
2082
|
|
|
2083
|
+
async openSkillsManager() {
|
|
2084
|
+
this.skillsSelectedNames = [];
|
|
2085
|
+
this.skillsKeyword = '';
|
|
2086
|
+
this.skillsStatusFilter = 'all';
|
|
2087
|
+
this.skillsImportList = [];
|
|
2088
|
+
this.skillsImportSelectedKeys = [];
|
|
2089
|
+
this.showSkillsModal = true;
|
|
2090
|
+
await this.refreshSkillsList({ silent: false });
|
|
2091
|
+
},
|
|
2092
|
+
|
|
2093
|
+
closeSkillsModal() {
|
|
2094
|
+
this.showSkillsModal = false;
|
|
2095
|
+
this.skillsSelectedNames = [];
|
|
2096
|
+
this.skillsImportSelectedKeys = [];
|
|
2097
|
+
},
|
|
2098
|
+
|
|
2099
|
+
async refreshSkillsList(options = {}) {
|
|
2100
|
+
this.skillsLoading = true;
|
|
2101
|
+
try {
|
|
2102
|
+
const res = await api('list-codex-skills');
|
|
2103
|
+
if (res.error) {
|
|
2104
|
+
this.showMessage(res.error, 'error');
|
|
2105
|
+
return;
|
|
2106
|
+
}
|
|
2107
|
+
this.skillsRootPath = res.root || '';
|
|
2108
|
+
this.skillsList = Array.isArray(res.items) ? res.items : [];
|
|
2109
|
+
const currentNames = new Set((Array.isArray(this.skillsList) ? this.skillsList : [])
|
|
2110
|
+
.map((item) => (item && typeof item.name === 'string' ? item.name.trim() : ''))
|
|
2111
|
+
.filter(Boolean));
|
|
2112
|
+
this.skillsSelectedNames = (Array.isArray(this.skillsSelectedNames) ? this.skillsSelectedNames : [])
|
|
2113
|
+
.filter((name) => currentNames.has(name));
|
|
2114
|
+
if (!options.silent) {
|
|
2115
|
+
const exists = res.exists !== false;
|
|
2116
|
+
if (!exists) {
|
|
2117
|
+
this.showMessage('skills 目录不存在,已按空列表显示', 'info');
|
|
2118
|
+
}
|
|
2119
|
+
}
|
|
2120
|
+
} catch (e) {
|
|
2121
|
+
this.showMessage('加载 skills 失败', 'error');
|
|
2122
|
+
} finally {
|
|
2123
|
+
this.skillsLoading = false;
|
|
2124
|
+
}
|
|
2125
|
+
},
|
|
2126
|
+
|
|
2127
|
+
resetSkillsFilters() {
|
|
2128
|
+
this.skillsKeyword = '';
|
|
2129
|
+
this.skillsStatusFilter = 'all';
|
|
2130
|
+
},
|
|
2131
|
+
|
|
2132
|
+
toggleAllSkillsSelection() {
|
|
2133
|
+
const selectable = this.skillsSelectableNames;
|
|
2134
|
+
if (this.skillsAllSelected) {
|
|
2135
|
+
const selectedSet = new Set(Array.isArray(this.skillsSelectedNames) ? this.skillsSelectedNames : []);
|
|
2136
|
+
selectable.forEach((name) => selectedSet.delete(name));
|
|
2137
|
+
this.skillsSelectedNames = Array.from(selectedSet);
|
|
2138
|
+
return;
|
|
2139
|
+
}
|
|
2140
|
+
const selectedSet = new Set(Array.isArray(this.skillsSelectedNames) ? this.skillsSelectedNames : []);
|
|
2141
|
+
selectable.forEach((name) => selectedSet.add(name));
|
|
2142
|
+
this.skillsSelectedNames = Array.from(selectedSet);
|
|
2143
|
+
},
|
|
2144
|
+
|
|
2145
|
+
buildSkillImportKey(item) {
|
|
2146
|
+
const safe = item && typeof item === 'object' ? item : {};
|
|
2147
|
+
const sourceApp = typeof safe.sourceApp === 'string' ? safe.sourceApp.trim().toLowerCase() : '';
|
|
2148
|
+
const name = typeof safe.name === 'string' ? safe.name.trim() : '';
|
|
2149
|
+
if (!sourceApp || !name) return '';
|
|
2150
|
+
return `${sourceApp}:${name}`;
|
|
2151
|
+
},
|
|
2152
|
+
|
|
2153
|
+
toggleAllSkillsImportSelection() {
|
|
2154
|
+
const selectable = this.skillsImportSelectableKeys;
|
|
2155
|
+
if (this.skillsImportAllSelected) {
|
|
2156
|
+
this.skillsImportSelectedKeys = [];
|
|
2157
|
+
return;
|
|
2158
|
+
}
|
|
2159
|
+
this.skillsImportSelectedKeys = [...selectable];
|
|
2160
|
+
},
|
|
2161
|
+
|
|
2162
|
+
async scanImportableSkills() {
|
|
2163
|
+
if (this.skillsScanningImports || this.skillsImporting) return;
|
|
2164
|
+
this.skillsScanningImports = true;
|
|
2165
|
+
try {
|
|
2166
|
+
const res = await api('scan-unmanaged-codex-skills');
|
|
2167
|
+
if (res.error) {
|
|
2168
|
+
this.showMessage(res.error, 'error');
|
|
2169
|
+
return;
|
|
2170
|
+
}
|
|
2171
|
+
this.skillsImportList = Array.isArray(res.items) ? res.items : [];
|
|
2172
|
+
const availableKeys = new Set(this.skillsImportSelectableKeys);
|
|
2173
|
+
this.skillsImportSelectedKeys = (Array.isArray(this.skillsImportSelectedKeys) ? this.skillsImportSelectedKeys : [])
|
|
2174
|
+
.filter((key) => availableKeys.has(key));
|
|
2175
|
+
if (this.skillsImportList.length === 0) {
|
|
2176
|
+
this.showMessage('未扫描到可导入 skill', 'info');
|
|
2177
|
+
} else {
|
|
2178
|
+
this.showMessage(`扫描到 ${this.skillsImportList.length} 个可导入 skill`, 'success');
|
|
2179
|
+
}
|
|
2180
|
+
} catch (e) {
|
|
2181
|
+
this.showMessage('扫描可导入 skill 失败', 'error');
|
|
2182
|
+
} finally {
|
|
2183
|
+
this.skillsScanningImports = false;
|
|
2184
|
+
}
|
|
2185
|
+
},
|
|
2186
|
+
|
|
2187
|
+
async importSelectedSkills() {
|
|
2188
|
+
if (this.skillsImporting) return;
|
|
2189
|
+
const selectedSet = new Set(Array.isArray(this.skillsImportSelectedKeys) ? this.skillsImportSelectedKeys : []);
|
|
2190
|
+
const selectedItems = (Array.isArray(this.skillsImportList) ? this.skillsImportList : [])
|
|
2191
|
+
.filter((item) => selectedSet.has(this.buildSkillImportKey(item)))
|
|
2192
|
+
.map((item) => ({
|
|
2193
|
+
name: item.name,
|
|
2194
|
+
sourceApp: item.sourceApp
|
|
2195
|
+
}));
|
|
2196
|
+
if (!selectedItems.length) {
|
|
2197
|
+
this.showMessage('请先选择要导入的 skill', 'error');
|
|
2198
|
+
return;
|
|
2199
|
+
}
|
|
2200
|
+
|
|
2201
|
+
this.skillsImporting = true;
|
|
2202
|
+
try {
|
|
2203
|
+
const res = await api('import-codex-skills', { items: selectedItems });
|
|
2204
|
+
if (res.error) {
|
|
2205
|
+
this.showMessage(res.error, 'error');
|
|
2206
|
+
return;
|
|
2207
|
+
}
|
|
2208
|
+
const importedCount = Array.isArray(res.imported) ? res.imported.length : 0;
|
|
2209
|
+
const failedCount = Array.isArray(res.failed) ? res.failed.length : 0;
|
|
2210
|
+
if (failedCount > 0 && importedCount > 0) {
|
|
2211
|
+
this.showMessage(`已导入 ${importedCount} 个,失败 ${failedCount} 个`, 'error');
|
|
2212
|
+
} else if (failedCount > 0) {
|
|
2213
|
+
const first = res.failed[0] && res.failed[0].error ? res.failed[0].error : '导入失败';
|
|
2214
|
+
this.showMessage(first, 'error');
|
|
2215
|
+
} else {
|
|
2216
|
+
this.showMessage(`已导入 ${importedCount} 个 skill`, 'success');
|
|
2217
|
+
}
|
|
2218
|
+
await this.refreshSkillsList({ silent: true });
|
|
2219
|
+
} catch (e) {
|
|
2220
|
+
this.showMessage('导入 skill 失败', 'error');
|
|
2221
|
+
} finally {
|
|
2222
|
+
this.skillsImporting = false;
|
|
2223
|
+
await this.scanImportableSkills();
|
|
2224
|
+
}
|
|
2225
|
+
},
|
|
2226
|
+
|
|
2227
|
+
async deleteSelectedSkills() {
|
|
2228
|
+
if (this.skillsDeleting) return;
|
|
2229
|
+
const selected = Array.isArray(this.skillsSelectedNames)
|
|
2230
|
+
? Array.from(new Set(this.skillsSelectedNames.map((item) => String(item || '').trim()).filter(Boolean)))
|
|
2231
|
+
: [];
|
|
2232
|
+
if (!selected.length) {
|
|
2233
|
+
this.showMessage('请先选择要删除的 skill', 'error');
|
|
2234
|
+
return;
|
|
2235
|
+
}
|
|
2236
|
+
const confirmed = window.confirm(`确认删除 ${selected.length} 个 skill 吗?此操作不可撤销。`);
|
|
2237
|
+
if (!confirmed) {
|
|
2238
|
+
return;
|
|
2239
|
+
}
|
|
2240
|
+
|
|
2241
|
+
this.skillsDeleting = true;
|
|
2242
|
+
try {
|
|
2243
|
+
const res = await api('delete-codex-skills', { names: selected });
|
|
2244
|
+
if (res.error) {
|
|
2245
|
+
this.showMessage(res.error, 'error');
|
|
2246
|
+
return;
|
|
2247
|
+
}
|
|
2248
|
+
|
|
2249
|
+
const deletedCount = Array.isArray(res.deleted) ? res.deleted.length : 0;
|
|
2250
|
+
const failedList = Array.isArray(res.failed) ? res.failed : [];
|
|
2251
|
+
const failedCount = failedList.length;
|
|
2252
|
+
if (failedCount > 0 && deletedCount > 0) {
|
|
2253
|
+
this.showMessage(`已删除 ${deletedCount} 个,失败 ${failedCount} 个`, 'error');
|
|
2254
|
+
} else if (failedCount > 0) {
|
|
2255
|
+
const first = failedList[0] && failedList[0].error ? failedList[0].error : '删除失败';
|
|
2256
|
+
this.showMessage(first, 'error');
|
|
2257
|
+
} else {
|
|
2258
|
+
this.showMessage(`已删除 ${deletedCount} 个 skill`, 'success');
|
|
2259
|
+
}
|
|
2260
|
+
await this.refreshSkillsList({ silent: true });
|
|
2261
|
+
} catch (e) {
|
|
2262
|
+
this.showMessage('删除 skill 失败', 'error');
|
|
2263
|
+
} finally {
|
|
2264
|
+
this.skillsDeleting = false;
|
|
2265
|
+
}
|
|
2266
|
+
},
|
|
2267
|
+
|
|
1757
2268
|
async openOpenclawAgentsEditor() {
|
|
1758
2269
|
this.setAgentsModalContext('openclaw');
|
|
1759
2270
|
this.agentsLoading = true;
|
|
@@ -3860,4 +4371,3 @@
|
|
|
3860
4371
|
app.mount('#app');
|
|
3861
4372
|
});
|
|
3862
4373
|
|
|
3863
|
-
|