codexmate 0.0.17 → 0.0.18
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 +42 -23
- package/README.md +42 -23
- package/cli.js +1447 -157
- package/lib/text-diff.js +303 -0
- package/package.json +1 -1
- package/web-ui/app.js +1728 -202
- package/web-ui/index.html +306 -77
- package/web-ui/logic.mjs +390 -0
- package/web-ui/modules/skills.methods.mjs +7 -1
- package/web-ui/session-helpers.mjs +350 -0
- package/web-ui/styles.css +481 -11
package/web-ui/app.js
CHANGED
|
@@ -4,10 +4,13 @@
|
|
|
4
4
|
normalizeClaudeSettingsEnv,
|
|
5
5
|
matchClaudeConfigFromSettings,
|
|
6
6
|
findDuplicateClaudeConfigName,
|
|
7
|
+
buildAgentsDiffPreview,
|
|
8
|
+
buildAgentsDiffPreviewRequest,
|
|
9
|
+
isAgentsDiffPreviewPayloadTooLarge,
|
|
10
|
+
shouldApplyAgentsDiffPreviewResponse,
|
|
7
11
|
formatLatency,
|
|
8
12
|
buildSpeedTestIssue,
|
|
9
13
|
isSessionQueryEnabled,
|
|
10
|
-
buildSessionListParams,
|
|
11
14
|
normalizeSessionSource,
|
|
12
15
|
normalizeSessionPathFilter,
|
|
13
16
|
buildSessionFilterCacheState,
|
|
@@ -16,6 +19,12 @@
|
|
|
16
19
|
runLatestOnlyQueue,
|
|
17
20
|
shouldForceCompactLayoutMode
|
|
18
21
|
} from './logic.mjs';
|
|
22
|
+
import {
|
|
23
|
+
switchMainTab as switchMainTabHelper,
|
|
24
|
+
loadSessions as loadSessionsHelper,
|
|
25
|
+
loadActiveSessionDetail as loadActiveSessionDetailHelper,
|
|
26
|
+
loadMoreSessionMessages as loadMoreSessionMessagesHelper
|
|
27
|
+
} from './session-helpers.mjs';
|
|
19
28
|
import {
|
|
20
29
|
CONFIG_MODE_SET,
|
|
21
30
|
getProviderConfigModeMeta,
|
|
@@ -43,6 +52,8 @@ import { createSkillsMethods } from './modules/skills.methods.mjs';
|
|
|
43
52
|
const API_BASE = (location && location.origin && location.origin !== 'null')
|
|
44
53
|
? location.origin
|
|
45
54
|
: 'http://localhost:3737';
|
|
55
|
+
const SESSION_TRASH_LIST_LIMIT = 500;
|
|
56
|
+
const SESSION_TRASH_PAGE_SIZE = 200;
|
|
46
57
|
const DEFAULT_OPENCLAW_TEMPLATE = `{
|
|
47
58
|
// OpenClaw config (JSON5)
|
|
48
59
|
agent: {
|
|
@@ -55,15 +66,45 @@ import { createSkillsMethods } from './modules/skills.methods.mjs';
|
|
|
55
66
|
}
|
|
56
67
|
}`;
|
|
57
68
|
|
|
58
|
-
async function
|
|
59
|
-
|
|
69
|
+
async function postApi(action, params = {}) {
|
|
70
|
+
return await fetch(`${API_BASE}/api`, {
|
|
60
71
|
method: 'POST',
|
|
61
72
|
headers: { 'Content-Type': 'application/json' },
|
|
62
73
|
body: JSON.stringify({ action, params })
|
|
63
74
|
});
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
async function api(action, params = {}) {
|
|
78
|
+
const res = await postApi(action, params);
|
|
64
79
|
return await res.json();
|
|
65
80
|
}
|
|
66
81
|
|
|
82
|
+
async function apiWithMeta(action, params = {}) {
|
|
83
|
+
const res = await postApi(action, params);
|
|
84
|
+
const contentType = String(res.headers.get('content-type') || '').toLowerCase();
|
|
85
|
+
if (contentType.includes('application/json')) {
|
|
86
|
+
try {
|
|
87
|
+
const payload = await res.json();
|
|
88
|
+
if (payload && typeof payload === 'object' && !Array.isArray(payload)) {
|
|
89
|
+
return { ...payload, ok: res.ok, status: res.status };
|
|
90
|
+
}
|
|
91
|
+
return { ok: res.ok, status: res.status, data: payload };
|
|
92
|
+
} catch (error) {
|
|
93
|
+
if (res.status === 413) {
|
|
94
|
+
return { ok: false, status: 413, errorCode: 'payload-too-large' };
|
|
95
|
+
}
|
|
96
|
+
throw error;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
const error = await res.text();
|
|
100
|
+
return {
|
|
101
|
+
ok: res.ok,
|
|
102
|
+
status: res.status,
|
|
103
|
+
error,
|
|
104
|
+
errorCode: res.status === 413 ? 'payload-too-large' : ''
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
|
|
67
108
|
const app = createApp({
|
|
68
109
|
data() {
|
|
69
110
|
return {
|
|
@@ -97,6 +138,13 @@ import { createSkillsMethods } from './modules/skills.methods.mjs';
|
|
|
97
138
|
showAgentsModal: false,
|
|
98
139
|
showSkillsModal: false,
|
|
99
140
|
showInstallModal: false,
|
|
141
|
+
showConfirmDialog: false,
|
|
142
|
+
confirmDialogTitle: '',
|
|
143
|
+
confirmDialogMessage: '',
|
|
144
|
+
confirmDialogConfirmText: '确认',
|
|
145
|
+
confirmDialogCancelText: '取消',
|
|
146
|
+
confirmDialogDanger: false,
|
|
147
|
+
confirmDialogResolver: null,
|
|
100
148
|
configTemplateContent: '',
|
|
101
149
|
configTemplateApplying: false,
|
|
102
150
|
codexApplying: false,
|
|
@@ -106,6 +154,19 @@ import { createSkillsMethods } from './modules/skills.methods.mjs';
|
|
|
106
154
|
agentsLineEnding: '\n',
|
|
107
155
|
agentsLoading: false,
|
|
108
156
|
agentsSaving: false,
|
|
157
|
+
agentsOriginalContent: '',
|
|
158
|
+
agentsDiffVisible: false,
|
|
159
|
+
agentsDiffLoading: false,
|
|
160
|
+
agentsDiffError: '',
|
|
161
|
+
agentsDiffLines: [],
|
|
162
|
+
agentsDiffStats: {
|
|
163
|
+
added: 0,
|
|
164
|
+
removed: 0,
|
|
165
|
+
unchanged: 0
|
|
166
|
+
},
|
|
167
|
+
agentsDiffTruncated: false,
|
|
168
|
+
agentsDiffHasChangesValue: false,
|
|
169
|
+
agentsDiffFingerprint: '',
|
|
109
170
|
agentsContext: 'codex',
|
|
110
171
|
agentsModalTitle: 'AGENTS.md 编辑器',
|
|
111
172
|
agentsModalHint: '保存后会写入目标 AGENTS.md(与 config.toml 同级)。',
|
|
@@ -122,7 +183,9 @@ import { createSkillsMethods } from './modules/skills.methods.mjs';
|
|
|
122
183
|
skillsImporting: false,
|
|
123
184
|
skillsZipImporting: false,
|
|
124
185
|
skillsExporting: false,
|
|
186
|
+
sessionPinnedMap: {},
|
|
125
187
|
sessionsList: [],
|
|
188
|
+
sessionsLoadedOnce: false,
|
|
126
189
|
sessionsLoading: false,
|
|
127
190
|
sessionFilterSource: 'all',
|
|
128
191
|
sessionPathFilter: '',
|
|
@@ -152,13 +215,31 @@ import { createSkillsMethods } from './modules/skills.methods.mjs';
|
|
|
152
215
|
activeSessionDetailClipped: false,
|
|
153
216
|
sessionDetailLoading: false,
|
|
154
217
|
sessionDetailRequestSeq: 0,
|
|
218
|
+
sessionDetailInitialMessageLimit: 80,
|
|
219
|
+
sessionDetailFetchStep: 80,
|
|
220
|
+
sessionDetailMessageLimit: 80,
|
|
221
|
+
sessionDetailMessageLimitCap: 1000,
|
|
155
222
|
sessionTimelineActiveKey: '',
|
|
156
223
|
sessionTimelineRafId: 0,
|
|
224
|
+
sessionTimelineLastSyncAt: 0,
|
|
225
|
+
sessionTimelineLastScrollTop: 0,
|
|
226
|
+
sessionTimelineLastAnchorY: 0,
|
|
227
|
+
sessionTimelineLastDirection: 0,
|
|
228
|
+
sessionTimelineEnabled: true,
|
|
157
229
|
sessionMessageRefMap: Object.create(null),
|
|
230
|
+
sessionMessageRefBinderMap: Object.create(null),
|
|
158
231
|
sessionPreviewScrollEl: null,
|
|
159
232
|
sessionPreviewContainerEl: null,
|
|
160
233
|
sessionPreviewHeaderEl: null,
|
|
161
234
|
sessionPreviewHeaderResizeObserver: null,
|
|
235
|
+
sessionListRenderEnabled: false,
|
|
236
|
+
sessionPreviewRenderEnabled: false,
|
|
237
|
+
sessionTabRenderTicket: 0,
|
|
238
|
+
sessionPreviewVisibleCount: 0,
|
|
239
|
+
sessionPreviewInitialBatchSize: 12,
|
|
240
|
+
sessionPreviewLoadStep: 24,
|
|
241
|
+
sessionPreviewPendingVisibleCount: 0,
|
|
242
|
+
sessionPreviewLoadingMore: false,
|
|
162
243
|
sessionStandalone: false,
|
|
163
244
|
sessionStandaloneError: '',
|
|
164
245
|
sessionStandaloneText: '',
|
|
@@ -275,6 +356,22 @@ import { createSkillsMethods } from './modules/skills.methods.mjs';
|
|
|
275
356
|
codexDownloadLoading: false,
|
|
276
357
|
codexDownloadProgress: 0,
|
|
277
358
|
codexDownloadTimer: null,
|
|
359
|
+
settingsTab: 'backup',
|
|
360
|
+
sessionTrashItems: [],
|
|
361
|
+
sessionTrashVisibleCount: SESSION_TRASH_PAGE_SIZE,
|
|
362
|
+
sessionTrashTotalCount: 0,
|
|
363
|
+
sessionTrashCountLoadedOnce: false,
|
|
364
|
+
sessionTrashLoadedOnce: false,
|
|
365
|
+
sessionTrashLastLoadFailed: false,
|
|
366
|
+
sessionTrashCountRequestToken: 0,
|
|
367
|
+
sessionTrashListRequestToken: 0,
|
|
368
|
+
sessionTrashCountPendingOptions: null,
|
|
369
|
+
sessionTrashPendingOptions: null,
|
|
370
|
+
sessionTrashCountLoading: false,
|
|
371
|
+
sessionTrashLoading: false,
|
|
372
|
+
sessionTrashRestoring: {},
|
|
373
|
+
sessionTrashPurging: {},
|
|
374
|
+
sessionTrashClearing: false,
|
|
278
375
|
claudeImportLoading: false,
|
|
279
376
|
codexImportLoading: false,
|
|
280
377
|
codexAuthProfiles: [],
|
|
@@ -309,7 +406,10 @@ import { createSkillsMethods } from './modules/skills.methods.mjs';
|
|
|
309
406
|
this.sessionResumeWithYolo = true;
|
|
310
407
|
}
|
|
311
408
|
this.restoreSessionFilterCache();
|
|
409
|
+
this.restoreSessionPinnedMap();
|
|
312
410
|
window.addEventListener('resize', this.onWindowResize);
|
|
411
|
+
window.addEventListener('keydown', this.handleGlobalKeydown);
|
|
412
|
+
window.addEventListener('beforeunload', this.handleBeforeUnload);
|
|
313
413
|
const savedConfigs = localStorage.getItem('claudeConfigs');
|
|
314
414
|
if (savedConfigs) {
|
|
315
415
|
try {
|
|
@@ -346,25 +446,104 @@ import { createSkillsMethods } from './modules/skills.methods.mjs';
|
|
|
346
446
|
this.loadAll();
|
|
347
447
|
},
|
|
348
448
|
beforeUnmount() {
|
|
349
|
-
this.
|
|
449
|
+
this.teardownSessionTabRender();
|
|
450
|
+
this.cancelScheduledSessionTabDeferredTeardown();
|
|
350
451
|
this.disconnectSessionPreviewHeaderResizeObserver();
|
|
351
452
|
window.removeEventListener('resize', this.onWindowResize);
|
|
453
|
+
window.removeEventListener('keydown', this.handleGlobalKeydown);
|
|
454
|
+
window.removeEventListener('beforeunload', this.handleBeforeUnload);
|
|
352
455
|
this.applyCompactLayoutClass(false);
|
|
353
456
|
this.sessionPreviewScrollEl = null;
|
|
354
457
|
this.sessionPreviewContainerEl = null;
|
|
355
458
|
this.sessionPreviewHeaderEl = null;
|
|
356
|
-
this.
|
|
459
|
+
this.clearSessionTimelineRefs();
|
|
357
460
|
},
|
|
358
461
|
|
|
359
462
|
computed: {
|
|
360
463
|
isSessionQueryEnabled() {
|
|
361
464
|
return isSessionQueryEnabled(this.sessionFilterSource);
|
|
362
465
|
},
|
|
466
|
+
activeSessionExportKey() {
|
|
467
|
+
return this.activeSession ? this.getSessionExportKey(this.activeSession) : '';
|
|
468
|
+
},
|
|
469
|
+
sortedSessionsList() {
|
|
470
|
+
const list = Array.isArray(this.sessionsList) ? this.sessionsList : [];
|
|
471
|
+
if (list.length === 0) return [];
|
|
472
|
+
const pinnedMap = (this.sessionPinnedMap && typeof this.sessionPinnedMap === 'object')
|
|
473
|
+
? this.sessionPinnedMap
|
|
474
|
+
: {};
|
|
475
|
+
let hasPinned = false;
|
|
476
|
+
const decorated = list.map((session, index) => {
|
|
477
|
+
const key = session ? this.getSessionExportKey(session) : '';
|
|
478
|
+
const rawPinnedAt = key ? pinnedMap[key] : 0;
|
|
479
|
+
const pinnedAt = Number.isFinite(Number(rawPinnedAt))
|
|
480
|
+
? Math.floor(Number(rawPinnedAt))
|
|
481
|
+
: 0;
|
|
482
|
+
const isPinned = pinnedAt > 0;
|
|
483
|
+
if (isPinned) {
|
|
484
|
+
hasPinned = true;
|
|
485
|
+
}
|
|
486
|
+
return { session, index, pinnedAt, isPinned };
|
|
487
|
+
});
|
|
488
|
+
if (!hasPinned) return list;
|
|
489
|
+
decorated.sort((a, b) => {
|
|
490
|
+
if (a.isPinned !== b.isPinned) return a.isPinned ? -1 : 1;
|
|
491
|
+
if (a.isPinned && a.pinnedAt !== b.pinnedAt) return b.pinnedAt - a.pinnedAt;
|
|
492
|
+
return a.index - b.index;
|
|
493
|
+
});
|
|
494
|
+
return decorated.map(item => item.session);
|
|
495
|
+
},
|
|
496
|
+
activeSessionVisibleMessages() {
|
|
497
|
+
if (this.mainTab !== 'sessions' || !this.sessionPreviewRenderEnabled) {
|
|
498
|
+
return [];
|
|
499
|
+
}
|
|
500
|
+
const list = Array.isArray(this.activeSessionMessages) ? this.activeSessionMessages : [];
|
|
501
|
+
const rawCount = Number(this.sessionPreviewVisibleCount);
|
|
502
|
+
const visibleCount = Number.isFinite(rawCount)
|
|
503
|
+
? Math.max(0, Math.floor(rawCount))
|
|
504
|
+
: 0;
|
|
505
|
+
if (visibleCount <= 0) {
|
|
506
|
+
if (!list.length) return [];
|
|
507
|
+
// Defensive fallback: avoid getting stuck in "正在渲染会话内容..."
|
|
508
|
+
// when visible count has not been primed yet.
|
|
509
|
+
return list.slice(0, Math.min(8, list.length));
|
|
510
|
+
}
|
|
511
|
+
if (visibleCount >= list.length) return list;
|
|
512
|
+
return list.slice(0, visibleCount);
|
|
513
|
+
},
|
|
514
|
+
canLoadMoreSessionMessages() {
|
|
515
|
+
if (this.mainTab !== 'sessions' || !this.sessionPreviewRenderEnabled) {
|
|
516
|
+
return false;
|
|
517
|
+
}
|
|
518
|
+
const total = Array.isArray(this.activeSessionMessages) ? this.activeSessionMessages.length : 0;
|
|
519
|
+
const visible = Array.isArray(this.activeSessionVisibleMessages) ? this.activeSessionVisibleMessages.length : 0;
|
|
520
|
+
return total > visible;
|
|
521
|
+
},
|
|
522
|
+
sessionPreviewRemainingCount() {
|
|
523
|
+
const total = Array.isArray(this.activeSessionMessages) ? this.activeSessionMessages.length : 0;
|
|
524
|
+
const visible = Array.isArray(this.activeSessionVisibleMessages) ? this.activeSessionVisibleMessages.length : 0;
|
|
525
|
+
return Math.max(0, total - visible);
|
|
526
|
+
},
|
|
363
527
|
sessionTimelineNodes() {
|
|
364
|
-
|
|
528
|
+
if (this.mainTab !== 'sessions' || !this.sessionPreviewRenderEnabled) {
|
|
529
|
+
return [];
|
|
530
|
+
}
|
|
531
|
+
return buildSessionTimelineNodes(this.activeSessionVisibleMessages, {
|
|
365
532
|
getKey: (message, index) => this.getRecordRenderKey(message, index)
|
|
366
533
|
});
|
|
367
534
|
},
|
|
535
|
+
sessionTimelineNodeKeyMap() {
|
|
536
|
+
const nodes = Array.isArray(this.sessionTimelineNodes) ? this.sessionTimelineNodes : [];
|
|
537
|
+
if (!nodes.length) {
|
|
538
|
+
return Object.create(null);
|
|
539
|
+
}
|
|
540
|
+
const map = Object.create(null);
|
|
541
|
+
for (const node of nodes) {
|
|
542
|
+
if (!node || !node.key) continue;
|
|
543
|
+
map[node.key] = true;
|
|
544
|
+
}
|
|
545
|
+
return map;
|
|
546
|
+
},
|
|
368
547
|
sessionTimelineActiveTitle() {
|
|
369
548
|
if (!this.sessionTimelineActiveKey) return '';
|
|
370
549
|
const nodes = Array.isArray(this.sessionTimelineNodes) ? this.sessionTimelineNodes : [];
|
|
@@ -377,6 +556,15 @@ import { createSkillsMethods } from './modules/skills.methods.mjs';
|
|
|
377
556
|
}
|
|
378
557
|
return '当前来源暂不支持关键词检索';
|
|
379
558
|
},
|
|
559
|
+
agentsDiffHasChanges() {
|
|
560
|
+
if (this.agentsDiffTruncated) {
|
|
561
|
+
return !!this.agentsDiffHasChangesValue;
|
|
562
|
+
}
|
|
563
|
+
const stats = this.agentsDiffStats || {};
|
|
564
|
+
const added = Number(stats.added || 0);
|
|
565
|
+
const removed = Number(stats.removed || 0);
|
|
566
|
+
return added > 0 || removed > 0;
|
|
567
|
+
},
|
|
380
568
|
claudeModelHasList() {
|
|
381
569
|
return Array.isArray(this.claudeModels) && this.claudeModels.length > 0;
|
|
382
570
|
},
|
|
@@ -416,6 +604,29 @@ import { createSkillsMethods } from './modules/skills.methods.mjs';
|
|
|
416
604
|
installRegistryPreview() {
|
|
417
605
|
return this.resolveInstallRegistryUrl(this.installRegistryPreset, this.installRegistryCustom);
|
|
418
606
|
},
|
|
607
|
+
visibleSessionTrashItems() {
|
|
608
|
+
const items = Array.isArray(this.sessionTrashItems) ? this.sessionTrashItems : [];
|
|
609
|
+
const visibleCount = Number(this.sessionTrashVisibleCount);
|
|
610
|
+
const safeVisibleCount = Number.isFinite(visibleCount) && visibleCount > 0
|
|
611
|
+
? Math.floor(visibleCount)
|
|
612
|
+
: SESSION_TRASH_PAGE_SIZE;
|
|
613
|
+
return items.slice(0, safeVisibleCount);
|
|
614
|
+
},
|
|
615
|
+
sessionTrashHasMoreItems() {
|
|
616
|
+
const totalItems = Array.isArray(this.sessionTrashItems) ? this.sessionTrashItems.length : 0;
|
|
617
|
+
return this.visibleSessionTrashItems.length < totalItems;
|
|
618
|
+
},
|
|
619
|
+
sessionTrashHiddenCount() {
|
|
620
|
+
const totalItems = Array.isArray(this.sessionTrashItems) ? this.sessionTrashItems.length : 0;
|
|
621
|
+
return Math.max(0, totalItems - this.visibleSessionTrashItems.length);
|
|
622
|
+
},
|
|
623
|
+
sessionTrashCount() {
|
|
624
|
+
const totalCount = Number(this.sessionTrashTotalCount);
|
|
625
|
+
if (Number.isFinite(totalCount) && totalCount >= 0) {
|
|
626
|
+
return Math.max(0, Math.floor(totalCount));
|
|
627
|
+
}
|
|
628
|
+
return Array.isArray(this.sessionTrashItems) ? this.sessionTrashItems.length : 0;
|
|
629
|
+
},
|
|
419
630
|
...createSkillsComputed(),
|
|
420
631
|
|
|
421
632
|
...createConfigModeComputed(),
|
|
@@ -807,21 +1018,413 @@ import { createSkillsMethods } from './modules/skills.methods.mjs';
|
|
|
807
1018
|
const normalizedMode = typeof mode === 'string'
|
|
808
1019
|
? mode.trim().toLowerCase()
|
|
809
1020
|
: '';
|
|
810
|
-
this.mainTab = 'config';
|
|
811
1021
|
this.configMode = CONFIG_MODE_SET.has(normalizedMode) ? normalizedMode : 'codex';
|
|
812
|
-
if (this.
|
|
813
|
-
this.
|
|
1022
|
+
if (this.mainTab === 'config') {
|
|
1023
|
+
if (this.configMode === 'claude') {
|
|
1024
|
+
const expectedMainTab = 'config';
|
|
1025
|
+
const expectedConfigMode = 'claude';
|
|
1026
|
+
const refresh = () => {
|
|
1027
|
+
if (this.mainTab !== expectedMainTab || this.configMode !== expectedConfigMode) {
|
|
1028
|
+
return;
|
|
1029
|
+
}
|
|
1030
|
+
this.refreshClaudeModelContext();
|
|
1031
|
+
};
|
|
1032
|
+
if (typeof this.scheduleAfterFrame === 'function') {
|
|
1033
|
+
this.scheduleAfterFrame(refresh);
|
|
1034
|
+
} else {
|
|
1035
|
+
refresh();
|
|
1036
|
+
}
|
|
1037
|
+
}
|
|
1038
|
+
this.scheduleAfterFrame(() => {
|
|
1039
|
+
this.clearMainTabSwitchIntent('config');
|
|
1040
|
+
});
|
|
1041
|
+
return;
|
|
814
1042
|
}
|
|
1043
|
+
this.switchMainTab('config');
|
|
815
1044
|
},
|
|
816
1045
|
|
|
1046
|
+
ensureMainTabSwitchState() {
|
|
1047
|
+
if (this.__mainTabSwitchState) {
|
|
1048
|
+
return this.__mainTabSwitchState;
|
|
1049
|
+
}
|
|
1050
|
+
this.__mainTabSwitchState = {
|
|
1051
|
+
intent: '',
|
|
1052
|
+
pendingTarget: '',
|
|
1053
|
+
ticket: 0
|
|
1054
|
+
};
|
|
1055
|
+
return this.__mainTabSwitchState;
|
|
1056
|
+
},
|
|
1057
|
+
ensureImmediateNavDomState() {
|
|
1058
|
+
if (typeof document === 'undefined') {
|
|
1059
|
+
return {
|
|
1060
|
+
navNodes: [],
|
|
1061
|
+
sessionPanelEl: null
|
|
1062
|
+
};
|
|
1063
|
+
}
|
|
1064
|
+
if (!this.__immediateNavDomState) {
|
|
1065
|
+
this.__immediateNavDomState = {
|
|
1066
|
+
navNodes: [],
|
|
1067
|
+
sessionPanelEl: null
|
|
1068
|
+
};
|
|
1069
|
+
}
|
|
1070
|
+
const state = this.__immediateNavDomState;
|
|
1071
|
+
const needsNavRefresh = !Array.isArray(state.navNodes)
|
|
1072
|
+
|| !state.navNodes.length
|
|
1073
|
+
|| state.navNodes.some((node) => !node || !node.isConnected);
|
|
1074
|
+
if (needsNavRefresh) {
|
|
1075
|
+
state.navNodes = Array.from(document.querySelectorAll('[data-main-tab]'));
|
|
1076
|
+
}
|
|
1077
|
+
if (!state.sessionPanelEl || !state.sessionPanelEl.isConnected) {
|
|
1078
|
+
state.sessionPanelEl = document.getElementById('panel-sessions');
|
|
1079
|
+
}
|
|
1080
|
+
return state;
|
|
1081
|
+
},
|
|
1082
|
+
setMainTabSwitchIntent(tab) {
|
|
1083
|
+
const normalizedTab = typeof tab === 'string'
|
|
1084
|
+
? tab.trim().toLowerCase()
|
|
1085
|
+
: '';
|
|
1086
|
+
if (!normalizedTab) return;
|
|
1087
|
+
const state = this.ensureMainTabSwitchState();
|
|
1088
|
+
state.intent = normalizedTab;
|
|
1089
|
+
},
|
|
1090
|
+
applyImmediateNavIntent(tab, configMode = '') {
|
|
1091
|
+
if (typeof document === 'undefined') return;
|
|
1092
|
+
const normalizedTab = typeof tab === 'string' ? tab.trim().toLowerCase() : '';
|
|
1093
|
+
if (!normalizedTab) return;
|
|
1094
|
+
const normalizedMode = typeof configMode === 'string' ? configMode.trim().toLowerCase() : '';
|
|
1095
|
+
const domState = this.ensureImmediateNavDomState();
|
|
1096
|
+
const nodes = Array.isArray(domState.navNodes) ? domState.navNodes : [];
|
|
1097
|
+
for (const node of nodes) {
|
|
1098
|
+
if (!node || !node.classList) continue;
|
|
1099
|
+
const nodeTab = String(node.getAttribute('data-main-tab') || '').trim().toLowerCase();
|
|
1100
|
+
const nodeMode = String(node.getAttribute('data-config-mode') || '').trim().toLowerCase();
|
|
1101
|
+
let shouldActivate = nodeTab === normalizedTab;
|
|
1102
|
+
if (shouldActivate && normalizedTab === 'config') {
|
|
1103
|
+
shouldActivate = nodeMode ? nodeMode === normalizedMode : false;
|
|
1104
|
+
}
|
|
1105
|
+
node.classList.toggle('nav-intent-active', !!shouldActivate);
|
|
1106
|
+
node.classList.toggle('nav-intent-inactive', !shouldActivate);
|
|
1107
|
+
}
|
|
1108
|
+
},
|
|
1109
|
+
clearImmediateNavIntent() {
|
|
1110
|
+
if (typeof document === 'undefined') return;
|
|
1111
|
+
const domState = this.ensureImmediateNavDomState();
|
|
1112
|
+
const nodes = Array.isArray(domState.navNodes) ? domState.navNodes : [];
|
|
1113
|
+
for (const node of nodes) {
|
|
1114
|
+
if (!node || !node.classList) continue;
|
|
1115
|
+
node.classList.remove('nav-intent-active');
|
|
1116
|
+
node.classList.remove('nav-intent-inactive');
|
|
1117
|
+
}
|
|
1118
|
+
},
|
|
1119
|
+
setSessionPanelFastHidden(hidden) {
|
|
1120
|
+
if (typeof document === 'undefined') return;
|
|
1121
|
+
const domState = this.ensureImmediateNavDomState();
|
|
1122
|
+
const panel = domState.sessionPanelEl;
|
|
1123
|
+
if (!panel || !panel.classList) return;
|
|
1124
|
+
panel.classList.toggle('session-panel-fast-hidden', !!hidden);
|
|
1125
|
+
},
|
|
1126
|
+
isSessionPanelFastHidden() {
|
|
1127
|
+
if (typeof document === 'undefined') return false;
|
|
1128
|
+
const domState = this.ensureImmediateNavDomState();
|
|
1129
|
+
const panel = domState.sessionPanelEl;
|
|
1130
|
+
return !!(panel && panel.classList && panel.classList.contains('session-panel-fast-hidden'));
|
|
1131
|
+
},
|
|
1132
|
+
recordPointerNavCommit(kind, value) {
|
|
1133
|
+
const normalizedKind = typeof kind === 'string' ? kind.trim().toLowerCase() : '';
|
|
1134
|
+
const normalizedValue = typeof value === 'string' ? value.trim().toLowerCase() : '';
|
|
1135
|
+
if (!normalizedKind || !normalizedValue) {
|
|
1136
|
+
this.__pointerNavCommitState = null;
|
|
1137
|
+
return;
|
|
1138
|
+
}
|
|
1139
|
+
this.__pointerNavCommitState = {
|
|
1140
|
+
kind: normalizedKind,
|
|
1141
|
+
value: normalizedValue,
|
|
1142
|
+
at: Date.now()
|
|
1143
|
+
};
|
|
1144
|
+
},
|
|
1145
|
+
consumePointerNavCommit(kind, value) {
|
|
1146
|
+
const normalizedKind = typeof kind === 'string' ? kind.trim().toLowerCase() : '';
|
|
1147
|
+
const normalizedValue = typeof value === 'string' ? value.trim().toLowerCase() : '';
|
|
1148
|
+
const state = this.__pointerNavCommitState;
|
|
1149
|
+
this.__pointerNavCommitState = null;
|
|
1150
|
+
if (!state || !normalizedKind || !normalizedValue) {
|
|
1151
|
+
return false;
|
|
1152
|
+
}
|
|
1153
|
+
if (state.kind !== normalizedKind || state.value !== normalizedValue) {
|
|
1154
|
+
return false;
|
|
1155
|
+
}
|
|
1156
|
+
return (Date.now() - Number(state.at || 0)) <= 1000;
|
|
1157
|
+
},
|
|
1158
|
+
onMainTabPointerDown(tab) {
|
|
1159
|
+
const event = arguments.length > 1 ? arguments[1] : null;
|
|
1160
|
+
if (event && typeof event.button === 'number' && event.button !== 0) {
|
|
1161
|
+
return;
|
|
1162
|
+
}
|
|
1163
|
+
const normalizedTab = typeof tab === 'string' ? tab.trim().toLowerCase() : '';
|
|
1164
|
+
if (!normalizedTab) return;
|
|
1165
|
+
this.setMainTabSwitchIntent(normalizedTab);
|
|
1166
|
+
this.applyImmediateNavIntent(normalizedTab);
|
|
1167
|
+
const shouldHideSessionPanel = this.mainTab === 'sessions' && normalizedTab !== 'sessions';
|
|
1168
|
+
this.setSessionPanelFastHidden(shouldHideSessionPanel);
|
|
1169
|
+
const pointerType = event && typeof event.pointerType === 'string'
|
|
1170
|
+
? event.pointerType.trim().toLowerCase()
|
|
1171
|
+
: '';
|
|
1172
|
+
if (pointerType === 'touch') {
|
|
1173
|
+
return;
|
|
1174
|
+
}
|
|
1175
|
+
this.recordPointerNavCommit('main', normalizedTab);
|
|
1176
|
+
this.switchMainTab(normalizedTab);
|
|
1177
|
+
},
|
|
1178
|
+
onConfigTabPointerDown(mode) {
|
|
1179
|
+
const event = arguments.length > 1 ? arguments[1] : null;
|
|
1180
|
+
if (event && typeof event.button === 'number' && event.button !== 0) {
|
|
1181
|
+
return;
|
|
1182
|
+
}
|
|
1183
|
+
const normalizedMode = typeof mode === 'string' ? mode.trim().toLowerCase() : '';
|
|
1184
|
+
if (!normalizedMode) return;
|
|
1185
|
+
this.setMainTabSwitchIntent('config');
|
|
1186
|
+
this.applyImmediateNavIntent('config', normalizedMode);
|
|
1187
|
+
const shouldHideSessionPanel = this.mainTab === 'sessions';
|
|
1188
|
+
this.setSessionPanelFastHidden(shouldHideSessionPanel);
|
|
1189
|
+
const pointerType = event && typeof event.pointerType === 'string'
|
|
1190
|
+
? event.pointerType.trim().toLowerCase()
|
|
1191
|
+
: '';
|
|
1192
|
+
if (pointerType === 'touch') {
|
|
1193
|
+
return;
|
|
1194
|
+
}
|
|
1195
|
+
this.recordPointerNavCommit('config', normalizedMode);
|
|
1196
|
+
this.switchConfigMode(normalizedMode);
|
|
1197
|
+
},
|
|
1198
|
+
onMainTabClick(tab) {
|
|
1199
|
+
const normalizedTab = typeof tab === 'string' ? tab.trim().toLowerCase() : '';
|
|
1200
|
+
if (!normalizedTab) return;
|
|
1201
|
+
if (this.consumePointerNavCommit('main', normalizedTab)) return;
|
|
1202
|
+
this.switchMainTab(normalizedTab);
|
|
1203
|
+
},
|
|
1204
|
+
onConfigTabClick(mode) {
|
|
1205
|
+
const normalizedMode = typeof mode === 'string' ? mode.trim().toLowerCase() : '';
|
|
1206
|
+
if (!normalizedMode) return;
|
|
1207
|
+
if (this.consumePointerNavCommit('config', normalizedMode)) return;
|
|
1208
|
+
this.switchConfigMode(normalizedMode);
|
|
1209
|
+
},
|
|
1210
|
+
clearMainTabSwitchIntent(expectedTab = '') {
|
|
1211
|
+
const state = this.ensureMainTabSwitchState();
|
|
1212
|
+
if (expectedTab && state.intent && state.intent !== expectedTab) {
|
|
1213
|
+
return;
|
|
1214
|
+
}
|
|
1215
|
+
state.intent = '';
|
|
1216
|
+
state.pendingTarget = '';
|
|
1217
|
+
this.clearImmediateNavIntent();
|
|
1218
|
+
this.setSessionPanelFastHidden(false);
|
|
1219
|
+
},
|
|
1220
|
+
getMainTabForNav() {
|
|
1221
|
+
const state = this.ensureMainTabSwitchState();
|
|
1222
|
+
return state.intent || this.mainTab;
|
|
1223
|
+
},
|
|
1224
|
+
isMainTabNavActive(tab) {
|
|
1225
|
+
return this.getMainTabForNav() === tab;
|
|
1226
|
+
},
|
|
1227
|
+
isConfigModeNavActive(mode) {
|
|
1228
|
+
return this.isMainTabNavActive('config') && this.configMode === mode;
|
|
1229
|
+
},
|
|
817
1230
|
switchMainTab(tab) {
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
1231
|
+
const normalizedTab = typeof tab === 'string'
|
|
1232
|
+
? tab.trim().toLowerCase()
|
|
1233
|
+
: '';
|
|
1234
|
+
const targetTab = normalizedTab || tab;
|
|
1235
|
+
if (!targetTab) return;
|
|
1236
|
+
if (targetTab === 'sessions') {
|
|
1237
|
+
this.cancelScheduledSessionTabDeferredTeardown();
|
|
821
1238
|
}
|
|
822
|
-
|
|
823
|
-
|
|
1239
|
+
|
|
1240
|
+
this.setMainTabSwitchIntent(targetTab);
|
|
1241
|
+
if (targetTab === 'config') {
|
|
1242
|
+
this.applyImmediateNavIntent('config', this.configMode);
|
|
1243
|
+
} else {
|
|
1244
|
+
this.applyImmediateNavIntent(targetTab);
|
|
1245
|
+
}
|
|
1246
|
+
|
|
1247
|
+
const previousTab = this.mainTab;
|
|
1248
|
+
const switchState = this.ensureMainTabSwitchState();
|
|
1249
|
+
if (targetTab === previousTab) {
|
|
1250
|
+
switchState.ticket += 1;
|
|
1251
|
+
switchState.pendingTarget = '';
|
|
1252
|
+
this.scheduleAfterFrame(() => {
|
|
1253
|
+
this.clearMainTabSwitchIntent(normalizedTab);
|
|
1254
|
+
});
|
|
1255
|
+
return;
|
|
1256
|
+
}
|
|
1257
|
+
const isLeavingSessions = previousTab === 'sessions' && targetTab !== 'sessions';
|
|
1258
|
+
const shouldDeferApply = isLeavingSessions;
|
|
1259
|
+
if (isLeavingSessions && !this.isSessionPanelFastHidden()) {
|
|
1260
|
+
this.setSessionPanelFastHidden(true);
|
|
1261
|
+
}
|
|
1262
|
+
if (!shouldDeferApply) {
|
|
1263
|
+
switchState.ticket += 1;
|
|
1264
|
+
switchState.pendingTarget = '';
|
|
1265
|
+
const result = switchMainTabHelper.call(this, targetTab);
|
|
1266
|
+
this.scheduleAfterFrame(() => {
|
|
1267
|
+
this.clearMainTabSwitchIntent(normalizedTab);
|
|
1268
|
+
});
|
|
1269
|
+
return result;
|
|
1270
|
+
}
|
|
1271
|
+
|
|
1272
|
+
const ticket = ++switchState.ticket;
|
|
1273
|
+
switchState.pendingTarget = targetTab;
|
|
1274
|
+
this.scheduleAfterFrame(() => {
|
|
1275
|
+
const liveState = this.ensureMainTabSwitchState();
|
|
1276
|
+
if (ticket !== liveState.ticket) return;
|
|
1277
|
+
const pendingTarget = liveState.pendingTarget || targetTab;
|
|
1278
|
+
liveState.pendingTarget = '';
|
|
1279
|
+
switchMainTabHelper.call(this, pendingTarget);
|
|
1280
|
+
this.clearMainTabSwitchIntent(normalizedTab);
|
|
1281
|
+
});
|
|
1282
|
+
},
|
|
1283
|
+
|
|
1284
|
+
scheduleAfterFrame(task) {
|
|
1285
|
+
const callback = typeof task === 'function' ? task : () => {};
|
|
1286
|
+
if (typeof requestAnimationFrame === 'function') {
|
|
1287
|
+
requestAnimationFrame(callback);
|
|
1288
|
+
return;
|
|
1289
|
+
}
|
|
1290
|
+
setTimeout(callback, 16);
|
|
1291
|
+
},
|
|
1292
|
+
scheduleIdleTask(task, timeoutMs = 160) {
|
|
1293
|
+
const callback = typeof task === 'function' ? task : () => {};
|
|
1294
|
+
const timeout = Number.isFinite(timeoutMs)
|
|
1295
|
+
? Math.max(16, Math.floor(timeoutMs))
|
|
1296
|
+
: 160;
|
|
1297
|
+
if (typeof requestIdleCallback === 'function') {
|
|
1298
|
+
const id = requestIdleCallback(callback, { timeout });
|
|
1299
|
+
return {
|
|
1300
|
+
type: 'idle',
|
|
1301
|
+
id
|
|
1302
|
+
};
|
|
1303
|
+
}
|
|
1304
|
+
const id = setTimeout(callback, timeout);
|
|
1305
|
+
return {
|
|
1306
|
+
type: 'timeout',
|
|
1307
|
+
id
|
|
1308
|
+
};
|
|
1309
|
+
},
|
|
1310
|
+
cancelIdleTask(handle) {
|
|
1311
|
+
if (!handle || typeof handle !== 'object') return;
|
|
1312
|
+
const type = handle.type;
|
|
1313
|
+
const id = handle.id;
|
|
1314
|
+
if (type === 'idle') {
|
|
1315
|
+
if (typeof cancelIdleCallback === 'function') {
|
|
1316
|
+
cancelIdleCallback(id);
|
|
1317
|
+
} else {
|
|
1318
|
+
clearTimeout(id);
|
|
1319
|
+
}
|
|
1320
|
+
return;
|
|
1321
|
+
}
|
|
1322
|
+
if (type === 'timeout') {
|
|
1323
|
+
clearTimeout(id);
|
|
1324
|
+
}
|
|
1325
|
+
},
|
|
1326
|
+
scheduleSessionTabDeferredTeardown(task) {
|
|
1327
|
+
const callback = typeof task === 'function' ? task : () => {};
|
|
1328
|
+
this.cancelScheduledSessionTabDeferredTeardown();
|
|
1329
|
+
this.__sessionTabDeferredTeardownHandle = this.scheduleIdleTask(() => {
|
|
1330
|
+
this.__sessionTabDeferredTeardownHandle = null;
|
|
1331
|
+
callback();
|
|
1332
|
+
}, 180);
|
|
1333
|
+
},
|
|
1334
|
+
cancelScheduledSessionTabDeferredTeardown() {
|
|
1335
|
+
const handle = this.__sessionTabDeferredTeardownHandle || null;
|
|
1336
|
+
if (!handle) return;
|
|
1337
|
+
this.cancelIdleTask(handle);
|
|
1338
|
+
this.__sessionTabDeferredTeardownHandle = null;
|
|
1339
|
+
},
|
|
1340
|
+
|
|
1341
|
+
resetSessionPreviewMessageRender() {
|
|
1342
|
+
this.sessionPreviewVisibleCount = 0;
|
|
1343
|
+
this.invalidateSessionTimelineMeasurementCache();
|
|
1344
|
+
},
|
|
1345
|
+
|
|
1346
|
+
resetSessionDetailPagination() {
|
|
1347
|
+
const initialLimit = Number.isFinite(this.sessionDetailInitialMessageLimit)
|
|
1348
|
+
? Math.max(1, Math.floor(this.sessionDetailInitialMessageLimit))
|
|
1349
|
+
: 80;
|
|
1350
|
+
this.sessionDetailMessageLimit = initialLimit;
|
|
1351
|
+
this.sessionPreviewPendingVisibleCount = 0;
|
|
1352
|
+
},
|
|
1353
|
+
|
|
1354
|
+
primeSessionPreviewMessageRender() {
|
|
1355
|
+
this.sessionPreviewVisibleCount = 0;
|
|
1356
|
+
this.invalidateSessionTimelineMeasurementCache();
|
|
1357
|
+
if (this.mainTab !== 'sessions' || !this.sessionPreviewRenderEnabled) {
|
|
1358
|
+
return;
|
|
824
1359
|
}
|
|
1360
|
+
const total = Array.isArray(this.activeSessionMessages)
|
|
1361
|
+
? this.activeSessionMessages.length
|
|
1362
|
+
: 0;
|
|
1363
|
+
if (total <= 0) return;
|
|
1364
|
+
const baseSize = Number.isFinite(this.sessionPreviewInitialBatchSize)
|
|
1365
|
+
? Math.max(1, Math.floor(this.sessionPreviewInitialBatchSize))
|
|
1366
|
+
: 40;
|
|
1367
|
+
this.sessionPreviewVisibleCount = Math.min(baseSize, total);
|
|
1368
|
+
this.invalidateSessionTimelineMeasurementCache();
|
|
1369
|
+
},
|
|
1370
|
+
|
|
1371
|
+
async loadMoreSessionMessages(stepSize) {
|
|
1372
|
+
return loadMoreSessionMessagesHelper.call(this, stepSize);
|
|
1373
|
+
},
|
|
1374
|
+
|
|
1375
|
+
suspendSessionTabRender() {
|
|
1376
|
+
this.sessionTabRenderTicket += 1;
|
|
1377
|
+
this.sessionListRenderEnabled = false;
|
|
1378
|
+
this.sessionPreviewRenderEnabled = false;
|
|
1379
|
+
this.cancelSessionTimelineSync();
|
|
1380
|
+
this.sessionTimelineActiveKey = '';
|
|
1381
|
+
this.sessionTimelineLastSyncAt = 0;
|
|
1382
|
+
this.sessionTimelineLastScrollTop = 0;
|
|
1383
|
+
this.sessionTimelineLastAnchorY = 0;
|
|
1384
|
+
this.sessionTimelineLastDirection = 0;
|
|
1385
|
+
this.sessionPreviewScrollEl = null;
|
|
1386
|
+
this.sessionPreviewContainerEl = null;
|
|
1387
|
+
this.sessionPreviewHeaderEl = null;
|
|
1388
|
+
},
|
|
1389
|
+
|
|
1390
|
+
finalizeSessionTabTeardown() {
|
|
1391
|
+
this.resetSessionPreviewMessageRender();
|
|
1392
|
+
this.sessionPreviewPendingVisibleCount = 0;
|
|
1393
|
+
this.clearSessionTimelineRefs();
|
|
1394
|
+
},
|
|
1395
|
+
|
|
1396
|
+
teardownSessionTabRender() {
|
|
1397
|
+
this.suspendSessionTabRender();
|
|
1398
|
+
this.finalizeSessionTabTeardown();
|
|
1399
|
+
},
|
|
1400
|
+
|
|
1401
|
+
prepareSessionTabRender() {
|
|
1402
|
+
const ticket = ++this.sessionTabRenderTicket;
|
|
1403
|
+
this.sessionListRenderEnabled = false;
|
|
1404
|
+
this.sessionPreviewRenderEnabled = false;
|
|
1405
|
+
this.resetSessionPreviewMessageRender();
|
|
1406
|
+
|
|
1407
|
+
this.scheduleAfterFrame(() => {
|
|
1408
|
+
if (ticket !== this.sessionTabRenderTicket || this.mainTab !== 'sessions') {
|
|
1409
|
+
return;
|
|
1410
|
+
}
|
|
1411
|
+
this.sessionListRenderEnabled = true;
|
|
1412
|
+
|
|
1413
|
+
this.scheduleAfterFrame(() => {
|
|
1414
|
+
if (ticket !== this.sessionTabRenderTicket || this.mainTab !== 'sessions') {
|
|
1415
|
+
return;
|
|
1416
|
+
}
|
|
1417
|
+
this.sessionPreviewRenderEnabled = true;
|
|
1418
|
+
this.$nextTick(() => {
|
|
1419
|
+
if (ticket !== this.sessionTabRenderTicket || this.mainTab !== 'sessions') {
|
|
1420
|
+
return;
|
|
1421
|
+
}
|
|
1422
|
+
this.primeSessionPreviewMessageRender();
|
|
1423
|
+
this.updateSessionTimelineOffset();
|
|
1424
|
+
this.scheduleSessionTimelineSync();
|
|
1425
|
+
});
|
|
1426
|
+
});
|
|
1427
|
+
});
|
|
825
1428
|
},
|
|
826
1429
|
|
|
827
1430
|
getSessionStandaloneContext() {
|
|
@@ -868,6 +1471,7 @@ import { createSkillsMethods } from './modules/skills.methods.mjs';
|
|
|
868
1471
|
|
|
869
1472
|
this.sessionStandalone = true;
|
|
870
1473
|
this.mainTab = 'sessions';
|
|
1474
|
+
this.prepareSessionTabRender();
|
|
871
1475
|
|
|
872
1476
|
if (context.error || !context.params) {
|
|
873
1477
|
this.sessionStandaloneError = `会话链接参数不完整:${context.error || '参数解析失败'}`;
|
|
@@ -887,7 +1491,7 @@ import { createSkillsMethods } from './modules/skills.methods.mjs';
|
|
|
887
1491
|
this.activeSessionDetailClipped = false;
|
|
888
1492
|
this.cancelSessionTimelineSync();
|
|
889
1493
|
this.sessionTimelineActiveKey = '';
|
|
890
|
-
this.
|
|
1494
|
+
this.clearSessionTimelineRefs();
|
|
891
1495
|
this.sessionStandaloneError = '';
|
|
892
1496
|
this.sessionStandaloneText = '';
|
|
893
1497
|
this.sessionStandaloneTitle = this.activeSession.title || '会话';
|
|
@@ -1222,38 +1826,466 @@ import { createSkillsMethods } from './modules/skills.methods.mjs';
|
|
|
1222
1826
|
}
|
|
1223
1827
|
}
|
|
1224
1828
|
} catch (e) {
|
|
1225
|
-
this.showMessage('克隆失败', 'error');
|
|
1829
|
+
this.showMessage('克隆失败', 'error');
|
|
1830
|
+
} finally {
|
|
1831
|
+
this.sessionCloning[key] = false;
|
|
1832
|
+
}
|
|
1833
|
+
},
|
|
1834
|
+
|
|
1835
|
+
async deleteSession(session) {
|
|
1836
|
+
if (!this.isDeleteAvailable(session)) {
|
|
1837
|
+
this.showMessage('不支持此操作', 'error');
|
|
1838
|
+
return;
|
|
1839
|
+
}
|
|
1840
|
+
const key = this.getSessionExportKey(session);
|
|
1841
|
+
if (this.sessionDeleting[key]) {
|
|
1842
|
+
return;
|
|
1843
|
+
}
|
|
1844
|
+
this.sessionDeleting[key] = true;
|
|
1845
|
+
try {
|
|
1846
|
+
const res = await api('trash-session', {
|
|
1847
|
+
source: session.source,
|
|
1848
|
+
sessionId: session.sessionId,
|
|
1849
|
+
filePath: session.filePath
|
|
1850
|
+
});
|
|
1851
|
+
if (res.error) {
|
|
1852
|
+
this.showMessage(res.error, 'error');
|
|
1853
|
+
return;
|
|
1854
|
+
}
|
|
1855
|
+
this.removeSessionPin(session);
|
|
1856
|
+
this.invalidateSessionTrashRequests();
|
|
1857
|
+
this.showMessage('已移入回收站', 'success');
|
|
1858
|
+
if (this.sessionTrashLoadedOnce) {
|
|
1859
|
+
this.prependSessionTrashItem(this.buildSessionTrashItemFromSession(session, res), {
|
|
1860
|
+
totalCount: res && res.totalCount !== undefined ? res.totalCount : undefined
|
|
1861
|
+
});
|
|
1862
|
+
} else if (this.sessionTrashCountLoadedOnce) {
|
|
1863
|
+
this.sessionTrashTotalCount = this.normalizeSessionTrashTotalCount(
|
|
1864
|
+
res && res.totalCount !== undefined
|
|
1865
|
+
? res.totalCount
|
|
1866
|
+
: (this.normalizeSessionTrashTotalCount(this.sessionTrashTotalCount, this.sessionTrashItems) + 1),
|
|
1867
|
+
this.sessionTrashItems
|
|
1868
|
+
);
|
|
1869
|
+
} else {
|
|
1870
|
+
this.sessionTrashTotalCount = this.normalizeSessionTrashTotalCount(
|
|
1871
|
+
res && res.totalCount !== undefined
|
|
1872
|
+
? res.totalCount
|
|
1873
|
+
: (this.normalizeSessionTrashTotalCount(this.sessionTrashTotalCount, this.sessionTrashItems) + 1),
|
|
1874
|
+
this.sessionTrashItems
|
|
1875
|
+
);
|
|
1876
|
+
}
|
|
1877
|
+
await this.removeSessionFromCurrentList(session);
|
|
1878
|
+
} catch (e) {
|
|
1879
|
+
this.showMessage('删除失败', 'error');
|
|
1880
|
+
} finally {
|
|
1881
|
+
this.sessionDeleting[key] = false;
|
|
1882
|
+
}
|
|
1883
|
+
},
|
|
1884
|
+
|
|
1885
|
+
buildSessionTrashItemFromSession(session, result = {}) {
|
|
1886
|
+
const deletedAt = typeof result.deletedAt === 'string' && result.deletedAt
|
|
1887
|
+
? result.deletedAt
|
|
1888
|
+
: new Date().toISOString();
|
|
1889
|
+
const source = session && session.source === 'claude' ? 'claude' : 'codex';
|
|
1890
|
+
return {
|
|
1891
|
+
trashId: typeof result.trashId === 'string' ? result.trashId : '',
|
|
1892
|
+
source,
|
|
1893
|
+
sourceLabel: session && typeof session.sourceLabel === 'string' && session.sourceLabel
|
|
1894
|
+
? session.sourceLabel
|
|
1895
|
+
: (source === 'claude' ? 'Claude Code' : 'Codex'),
|
|
1896
|
+
sessionId: session && typeof session.sessionId === 'string' ? session.sessionId : '',
|
|
1897
|
+
title: session && typeof session.title === 'string' && session.title
|
|
1898
|
+
? session.title
|
|
1899
|
+
: (session && typeof session.sessionId === 'string' ? session.sessionId : ''),
|
|
1900
|
+
cwd: session && typeof session.cwd === 'string' ? session.cwd : '',
|
|
1901
|
+
createdAt: session && typeof session.createdAt === 'string' ? session.createdAt : '',
|
|
1902
|
+
updatedAt: session && typeof session.updatedAt === 'string' ? session.updatedAt : '',
|
|
1903
|
+
deletedAt,
|
|
1904
|
+
messageCount: Number.isFinite(Number(result && result.messageCount))
|
|
1905
|
+
? Math.max(0, Math.floor(Number(result.messageCount)))
|
|
1906
|
+
: (Number.isFinite(Number(session && session.messageCount))
|
|
1907
|
+
? Math.max(0, Math.floor(Number(session.messageCount)))
|
|
1908
|
+
: 0),
|
|
1909
|
+
originalFilePath: session && typeof session.filePath === 'string' ? session.filePath : '',
|
|
1910
|
+
provider: session && typeof session.provider === 'string' ? session.provider : source,
|
|
1911
|
+
keywords: Array.isArray(session && session.keywords) ? session.keywords : [],
|
|
1912
|
+
capabilities: session && typeof session.capabilities === 'object' && session.capabilities
|
|
1913
|
+
? session.capabilities
|
|
1914
|
+
: {},
|
|
1915
|
+
claudeIndexPath: '',
|
|
1916
|
+
claudeIndexEntry: null,
|
|
1917
|
+
trashFilePath: ''
|
|
1918
|
+
};
|
|
1919
|
+
},
|
|
1920
|
+
|
|
1921
|
+
prependSessionTrashItem(item, options = {}) {
|
|
1922
|
+
if (!item || !item.trashId) {
|
|
1923
|
+
return;
|
|
1924
|
+
}
|
|
1925
|
+
const existing = Array.isArray(this.sessionTrashItems) ? this.sessionTrashItems : [];
|
|
1926
|
+
const filtered = existing.filter((entry) => this.getSessionTrashActionKey(entry) !== item.trashId);
|
|
1927
|
+
const nextItems = [item, ...filtered].slice(0, SESSION_TRASH_LIST_LIMIT);
|
|
1928
|
+
const previousTotalCount = Number(this.sessionTrashTotalCount);
|
|
1929
|
+
const normalizedPreviousTotal = Number.isFinite(previousTotalCount) && previousTotalCount >= 0
|
|
1930
|
+
? Math.max(existing.length, Math.floor(previousTotalCount))
|
|
1931
|
+
: existing.length;
|
|
1932
|
+
this.sessionTrashItems = nextItems;
|
|
1933
|
+
const previousVisibleCount = Number(this.sessionTrashVisibleCount);
|
|
1934
|
+
const normalizedPreviousVisibleCount = Number.isFinite(previousVisibleCount) && previousVisibleCount > 0
|
|
1935
|
+
? Math.floor(previousVisibleCount)
|
|
1936
|
+
: SESSION_TRASH_PAGE_SIZE;
|
|
1937
|
+
const wasFullyExpanded = normalizedPreviousVisibleCount >= existing.length
|
|
1938
|
+
|| normalizedPreviousVisibleCount >= normalizedPreviousTotal;
|
|
1939
|
+
if (wasFullyExpanded) {
|
|
1940
|
+
this.sessionTrashVisibleCount = Math.min(
|
|
1941
|
+
normalizedPreviousVisibleCount + 1,
|
|
1942
|
+
nextItems.length || (normalizedPreviousVisibleCount + 1)
|
|
1943
|
+
);
|
|
1944
|
+
}
|
|
1945
|
+
const fallbackTotalCount = filtered.length === existing.length
|
|
1946
|
+
? normalizedPreviousTotal + 1
|
|
1947
|
+
: normalizedPreviousTotal;
|
|
1948
|
+
this.sessionTrashTotalCount = this.normalizeSessionTrashTotalCount(
|
|
1949
|
+
options && options.totalCount !== undefined
|
|
1950
|
+
? options.totalCount
|
|
1951
|
+
: fallbackTotalCount,
|
|
1952
|
+
nextItems
|
|
1953
|
+
);
|
|
1954
|
+
},
|
|
1955
|
+
|
|
1956
|
+
normalizeSessionTrashTotalCount(totalCount, fallbackItems = this.sessionTrashItems) {
|
|
1957
|
+
const fallbackCount = Array.isArray(fallbackItems) ? fallbackItems.length : 0;
|
|
1958
|
+
const numericTotal = Number(totalCount);
|
|
1959
|
+
if (!Number.isFinite(numericTotal) || numericTotal < 0) {
|
|
1960
|
+
return fallbackCount;
|
|
1961
|
+
}
|
|
1962
|
+
return Math.floor(numericTotal);
|
|
1963
|
+
},
|
|
1964
|
+
|
|
1965
|
+
getSessionTrashViewState() {
|
|
1966
|
+
if (this.sessionTrashLoading && !this.sessionTrashLoadedOnce) {
|
|
1967
|
+
return 'loading';
|
|
1968
|
+
}
|
|
1969
|
+
const totalCount = Number(this.sessionTrashCount);
|
|
1970
|
+
const normalizedTotalCount = Number.isFinite(totalCount) && totalCount >= 0
|
|
1971
|
+
? Math.floor(totalCount)
|
|
1972
|
+
: 0;
|
|
1973
|
+
const hasVisibleItems = Array.isArray(this.sessionTrashItems) && this.sessionTrashItems.length > 0;
|
|
1974
|
+
if (this.sessionTrashLastLoadFailed && (!this.sessionTrashLoadedOnce || !hasVisibleItems)) {
|
|
1975
|
+
return 'retry';
|
|
1976
|
+
}
|
|
1977
|
+
if (!this.sessionTrashLoadedOnce) {
|
|
1978
|
+
return normalizedTotalCount > 0 ? 'retry' : 'empty';
|
|
1979
|
+
}
|
|
1980
|
+
if (normalizedTotalCount === 0) {
|
|
1981
|
+
return 'empty';
|
|
1982
|
+
}
|
|
1983
|
+
return hasVisibleItems ? 'list' : 'retry';
|
|
1984
|
+
},
|
|
1985
|
+
|
|
1986
|
+
issueSessionTrashCountRequestToken() {
|
|
1987
|
+
const currentToken = Number(this.sessionTrashCountRequestToken);
|
|
1988
|
+
const nextToken = Number.isFinite(currentToken) && currentToken >= 0
|
|
1989
|
+
? Math.floor(currentToken) + 1
|
|
1990
|
+
: 1;
|
|
1991
|
+
this.sessionTrashCountRequestToken = nextToken;
|
|
1992
|
+
return nextToken;
|
|
1993
|
+
},
|
|
1994
|
+
|
|
1995
|
+
issueSessionTrashListRequestToken() {
|
|
1996
|
+
const currentToken = Number(this.sessionTrashListRequestToken);
|
|
1997
|
+
const nextToken = Number.isFinite(currentToken) && currentToken >= 0
|
|
1998
|
+
? Math.floor(currentToken) + 1
|
|
1999
|
+
: 1;
|
|
2000
|
+
this.sessionTrashListRequestToken = nextToken;
|
|
2001
|
+
return nextToken;
|
|
2002
|
+
},
|
|
2003
|
+
|
|
2004
|
+
invalidateSessionTrashRequests() {
|
|
2005
|
+
this.issueSessionTrashCountRequestToken();
|
|
2006
|
+
return this.issueSessionTrashListRequestToken();
|
|
2007
|
+
},
|
|
2008
|
+
|
|
2009
|
+
isLatestSessionTrashCountRequestToken(token) {
|
|
2010
|
+
return Number(token) === Number(this.sessionTrashCountRequestToken);
|
|
2011
|
+
},
|
|
2012
|
+
|
|
2013
|
+
isLatestSessionTrashListRequestToken(token) {
|
|
2014
|
+
return Number(token) === Number(this.sessionTrashListRequestToken);
|
|
2015
|
+
},
|
|
2016
|
+
|
|
2017
|
+
resetSessionTrashVisibleCount() {
|
|
2018
|
+
const totalItems = Array.isArray(this.sessionTrashItems) ? this.sessionTrashItems.length : 0;
|
|
2019
|
+
this.sessionTrashVisibleCount = Math.min(totalItems, SESSION_TRASH_PAGE_SIZE) || SESSION_TRASH_PAGE_SIZE;
|
|
2020
|
+
},
|
|
2021
|
+
|
|
2022
|
+
loadMoreSessionTrashItems() {
|
|
2023
|
+
const totalItems = Array.isArray(this.sessionTrashItems) ? this.sessionTrashItems.length : 0;
|
|
2024
|
+
const visibleCount = Number(this.sessionTrashVisibleCount);
|
|
2025
|
+
const safeVisibleCount = Number.isFinite(visibleCount) && visibleCount > 0
|
|
2026
|
+
? Math.floor(visibleCount)
|
|
2027
|
+
: SESSION_TRASH_PAGE_SIZE;
|
|
2028
|
+
this.sessionTrashVisibleCount = Math.min(totalItems, safeVisibleCount + SESSION_TRASH_PAGE_SIZE);
|
|
2029
|
+
},
|
|
2030
|
+
|
|
2031
|
+
clearActiveSessionState() {
|
|
2032
|
+
this.activeSession = null;
|
|
2033
|
+
this.activeSessionMessages = [];
|
|
2034
|
+
this.resetSessionDetailPagination();
|
|
2035
|
+
this.resetSessionPreviewMessageRender();
|
|
2036
|
+
this.activeSessionDetailError = '';
|
|
2037
|
+
this.activeSessionDetailClipped = false;
|
|
2038
|
+
this.cancelSessionTimelineSync();
|
|
2039
|
+
this.sessionTimelineActiveKey = '';
|
|
2040
|
+
this.clearSessionTimelineRefs();
|
|
2041
|
+
},
|
|
2042
|
+
|
|
2043
|
+
async removeSessionFromCurrentList(session) {
|
|
2044
|
+
const sessionKey = this.getSessionExportKey(session);
|
|
2045
|
+
if (!sessionKey) {
|
|
2046
|
+
return;
|
|
2047
|
+
}
|
|
2048
|
+
const currentList = Array.isArray(this.sessionsList) ? [...this.sessionsList] : [];
|
|
2049
|
+
const removedIndex = currentList.findIndex((item) => this.getSessionExportKey(item) === sessionKey);
|
|
2050
|
+
if (removedIndex < 0) {
|
|
2051
|
+
return;
|
|
2052
|
+
}
|
|
2053
|
+
const activeKey = this.activeSession ? this.getSessionExportKey(this.activeSession) : '';
|
|
2054
|
+
const renderedList = Array.isArray(this.sortedSessionsList) ? this.sortedSessionsList : [];
|
|
2055
|
+
const renderedIndex = renderedList.findIndex((item) => this.getSessionExportKey(item) === sessionKey);
|
|
2056
|
+
let nextActiveKey = '';
|
|
2057
|
+
if (activeKey === sessionKey && renderedIndex >= 0) {
|
|
2058
|
+
const fallbackSession = renderedList[renderedIndex - 1] || renderedList[renderedIndex + 1] || null;
|
|
2059
|
+
nextActiveKey = fallbackSession ? this.getSessionExportKey(fallbackSession) : '';
|
|
2060
|
+
}
|
|
2061
|
+
currentList.splice(removedIndex, 1);
|
|
2062
|
+
this.sessionsList = currentList;
|
|
2063
|
+
this.syncSessionPathOptionsForSource(
|
|
2064
|
+
this.sessionFilterSource,
|
|
2065
|
+
this.extractPathOptionsFromSessions(currentList),
|
|
2066
|
+
false
|
|
2067
|
+
);
|
|
2068
|
+
if (activeKey !== sessionKey) {
|
|
2069
|
+
return;
|
|
2070
|
+
}
|
|
2071
|
+
if (currentList.length === 0) {
|
|
2072
|
+
this.clearActiveSessionState();
|
|
2073
|
+
return;
|
|
2074
|
+
}
|
|
2075
|
+
const nextSession = currentList.find((item) => this.getSessionExportKey(item) === nextActiveKey)
|
|
2076
|
+
|| currentList[Math.min(removedIndex, currentList.length - 1)];
|
|
2077
|
+
if (!nextSession) {
|
|
2078
|
+
this.clearActiveSessionState();
|
|
2079
|
+
return;
|
|
2080
|
+
}
|
|
2081
|
+
await this.selectSession(nextSession);
|
|
2082
|
+
},
|
|
2083
|
+
|
|
2084
|
+
normalizeSettingsTab(tab) {
|
|
2085
|
+
return tab === 'trash' ? 'trash' : 'backup';
|
|
2086
|
+
},
|
|
2087
|
+
|
|
2088
|
+
async onSettingsTabClick(tab) {
|
|
2089
|
+
await this.switchSettingsTab(tab);
|
|
2090
|
+
},
|
|
2091
|
+
|
|
2092
|
+
async switchSettingsTab(tab, options = {}) {
|
|
2093
|
+
const nextTab = this.normalizeSettingsTab(tab);
|
|
2094
|
+
this.settingsTab = nextTab;
|
|
2095
|
+
if (nextTab !== 'trash') {
|
|
2096
|
+
return;
|
|
2097
|
+
}
|
|
2098
|
+
const forceRefresh = options.forceRefresh === true;
|
|
2099
|
+
if (forceRefresh || !this.sessionTrashLoadedOnce) {
|
|
2100
|
+
await this.loadSessionTrash({ forceRefresh });
|
|
2101
|
+
}
|
|
2102
|
+
},
|
|
2103
|
+
|
|
2104
|
+
async loadSessionTrashCount(options = {}) {
|
|
2105
|
+
if (this.sessionTrashCountLoading) {
|
|
2106
|
+
this.sessionTrashCountPendingOptions = {
|
|
2107
|
+
...(this.sessionTrashCountPendingOptions || {}),
|
|
2108
|
+
...(options || {})
|
|
2109
|
+
};
|
|
2110
|
+
return;
|
|
2111
|
+
}
|
|
2112
|
+
const requestToken = this.issueSessionTrashCountRequestToken();
|
|
2113
|
+
this.sessionTrashCountLoading = true;
|
|
2114
|
+
try {
|
|
2115
|
+
const res = await api('list-session-trash', { countOnly: true });
|
|
2116
|
+
if (!this.isLatestSessionTrashCountRequestToken(requestToken)) {
|
|
2117
|
+
return;
|
|
2118
|
+
}
|
|
2119
|
+
if (res.error) {
|
|
2120
|
+
if (options.silent !== true) {
|
|
2121
|
+
this.showMessage(res.error, 'error');
|
|
2122
|
+
}
|
|
2123
|
+
return;
|
|
2124
|
+
}
|
|
2125
|
+
this.sessionTrashTotalCount = this.normalizeSessionTrashTotalCount(
|
|
2126
|
+
res.totalCount,
|
|
2127
|
+
this.sessionTrashItems
|
|
2128
|
+
);
|
|
2129
|
+
this.sessionTrashCountLoadedOnce = true;
|
|
2130
|
+
} catch (e) {
|
|
2131
|
+
if (this.isLatestSessionTrashCountRequestToken(requestToken) && options.silent !== true) {
|
|
2132
|
+
this.showMessage('加载回收站数量失败', 'error');
|
|
2133
|
+
}
|
|
2134
|
+
} finally {
|
|
2135
|
+
this.sessionTrashCountLoading = false;
|
|
2136
|
+
const pendingOptions = this.sessionTrashCountPendingOptions;
|
|
2137
|
+
this.sessionTrashCountPendingOptions = null;
|
|
2138
|
+
if (pendingOptions) {
|
|
2139
|
+
await this.loadSessionTrashCount(pendingOptions);
|
|
2140
|
+
}
|
|
2141
|
+
}
|
|
2142
|
+
},
|
|
2143
|
+
|
|
2144
|
+
getSessionTrashActionKey(item) {
|
|
2145
|
+
return item && typeof item.trashId === 'string' ? item.trashId : '';
|
|
2146
|
+
},
|
|
2147
|
+
|
|
2148
|
+
isSessionTrashActionBusy(item) {
|
|
2149
|
+
const key = typeof item === 'string' ? item : this.getSessionTrashActionKey(item);
|
|
2150
|
+
return !!(key && (this.sessionTrashRestoring[key] || this.sessionTrashPurging[key]));
|
|
2151
|
+
},
|
|
2152
|
+
|
|
2153
|
+
async loadSessionTrash(options = {}) {
|
|
2154
|
+
if (this.sessionTrashLoading) {
|
|
2155
|
+
this.sessionTrashPendingOptions = {
|
|
2156
|
+
...(this.sessionTrashPendingOptions || {}),
|
|
2157
|
+
...(options || {})
|
|
2158
|
+
};
|
|
2159
|
+
return;
|
|
2160
|
+
}
|
|
2161
|
+
const requestToken = this.issueSessionTrashListRequestToken();
|
|
2162
|
+
this.sessionTrashLoading = true;
|
|
2163
|
+
this.sessionTrashLastLoadFailed = false;
|
|
2164
|
+
let loadSucceeded = false;
|
|
2165
|
+
try {
|
|
2166
|
+
const res = await api('list-session-trash', {
|
|
2167
|
+
limit: SESSION_TRASH_LIST_LIMIT,
|
|
2168
|
+
forceRefresh: !!options.forceRefresh
|
|
2169
|
+
});
|
|
2170
|
+
if (!this.isLatestSessionTrashListRequestToken(requestToken)) {
|
|
2171
|
+
return;
|
|
2172
|
+
}
|
|
2173
|
+
if (res.error) {
|
|
2174
|
+
this.sessionTrashLastLoadFailed = true;
|
|
2175
|
+
this.showMessage(res.error, 'error');
|
|
2176
|
+
return;
|
|
2177
|
+
}
|
|
2178
|
+
const nextItems = Array.isArray(res.items) ? res.items : [];
|
|
2179
|
+
this.sessionTrashItems = nextItems;
|
|
2180
|
+
this.resetSessionTrashVisibleCount();
|
|
2181
|
+
this.sessionTrashTotalCount = this.normalizeSessionTrashTotalCount(res.totalCount, nextItems);
|
|
2182
|
+
this.sessionTrashCountLoadedOnce = true;
|
|
2183
|
+
this.sessionTrashLastLoadFailed = false;
|
|
2184
|
+
loadSucceeded = true;
|
|
2185
|
+
} catch (e) {
|
|
2186
|
+
if (this.isLatestSessionTrashListRequestToken(requestToken)) {
|
|
2187
|
+
this.sessionTrashLastLoadFailed = true;
|
|
2188
|
+
this.showMessage('加载回收站失败', 'error');
|
|
2189
|
+
}
|
|
2190
|
+
} finally {
|
|
2191
|
+
this.sessionTrashLoading = false;
|
|
2192
|
+
if (loadSucceeded) {
|
|
2193
|
+
this.sessionTrashLoadedOnce = true;
|
|
2194
|
+
}
|
|
2195
|
+
const pendingOptions = this.sessionTrashPendingOptions;
|
|
2196
|
+
this.sessionTrashPendingOptions = null;
|
|
2197
|
+
if (pendingOptions) {
|
|
2198
|
+
await this.loadSessionTrash(pendingOptions);
|
|
2199
|
+
}
|
|
2200
|
+
}
|
|
2201
|
+
},
|
|
2202
|
+
|
|
2203
|
+
async restoreSessionTrash(item) {
|
|
2204
|
+
const key = this.getSessionTrashActionKey(item);
|
|
2205
|
+
if (!key || this.isSessionTrashActionBusy(key) || this.sessionTrashClearing) {
|
|
2206
|
+
return;
|
|
2207
|
+
}
|
|
2208
|
+
this.sessionTrashRestoring[key] = true;
|
|
2209
|
+
try {
|
|
2210
|
+
const res = await api('restore-session-trash', { trashId: key });
|
|
2211
|
+
if (res.error) {
|
|
2212
|
+
this.showMessage(res.error, 'error');
|
|
2213
|
+
return;
|
|
2214
|
+
}
|
|
2215
|
+
this.showMessage('会话已恢复', 'success');
|
|
2216
|
+
this.invalidateSessionTrashRequests();
|
|
2217
|
+
await this.loadSessionTrash({ forceRefresh: true });
|
|
2218
|
+
if (this.sessionsLoadedOnce) {
|
|
2219
|
+
await this.loadSessions();
|
|
2220
|
+
}
|
|
2221
|
+
} catch (e) {
|
|
2222
|
+
this.showMessage('恢复失败', 'error');
|
|
2223
|
+
} finally {
|
|
2224
|
+
this.sessionTrashRestoring[key] = false;
|
|
2225
|
+
}
|
|
2226
|
+
},
|
|
2227
|
+
|
|
2228
|
+
async purgeSessionTrash(item) {
|
|
2229
|
+
const key = this.getSessionTrashActionKey(item);
|
|
2230
|
+
if (!key || this.isSessionTrashActionBusy(key) || this.sessionTrashClearing) {
|
|
2231
|
+
return;
|
|
2232
|
+
}
|
|
2233
|
+
const confirmed = await this.requestConfirmDialog({
|
|
2234
|
+
title: '彻底删除回收站记录',
|
|
2235
|
+
message: '该会话将从回收站永久删除,且无法恢复。',
|
|
2236
|
+
confirmText: '彻底删除',
|
|
2237
|
+
cancelText: '取消',
|
|
2238
|
+
danger: true
|
|
2239
|
+
});
|
|
2240
|
+
if (!confirmed) {
|
|
2241
|
+
return;
|
|
2242
|
+
}
|
|
2243
|
+
this.sessionTrashPurging[key] = true;
|
|
2244
|
+
try {
|
|
2245
|
+
const res = await api('purge-session-trash', { trashId: key });
|
|
2246
|
+
if (res.error) {
|
|
2247
|
+
this.showMessage(res.error, 'error');
|
|
2248
|
+
return;
|
|
2249
|
+
}
|
|
2250
|
+
this.showMessage('已彻底删除', 'success');
|
|
2251
|
+
this.invalidateSessionTrashRequests();
|
|
2252
|
+
await this.loadSessionTrash({ forceRefresh: true });
|
|
2253
|
+
} catch (e) {
|
|
2254
|
+
this.showMessage('彻底删除失败', 'error');
|
|
1226
2255
|
} finally {
|
|
1227
|
-
this.
|
|
2256
|
+
this.sessionTrashPurging[key] = false;
|
|
1228
2257
|
}
|
|
1229
2258
|
},
|
|
1230
2259
|
|
|
1231
|
-
async
|
|
1232
|
-
|
|
1233
|
-
|
|
2260
|
+
async clearSessionTrash() {
|
|
2261
|
+
const normalizedCount = Number(this.sessionTrashCount);
|
|
2262
|
+
if (this.sessionTrashClearing || !Number.isFinite(normalizedCount) || normalizedCount <= 0) {
|
|
1234
2263
|
return;
|
|
1235
2264
|
}
|
|
1236
|
-
const
|
|
1237
|
-
|
|
2265
|
+
const confirmed = await this.requestConfirmDialog({
|
|
2266
|
+
title: '清空回收站',
|
|
2267
|
+
message: '该操作会永久删除回收站中的全部会话,且无法恢复。',
|
|
2268
|
+
confirmText: '全部清空',
|
|
2269
|
+
cancelText: '取消',
|
|
2270
|
+
danger: true
|
|
2271
|
+
});
|
|
2272
|
+
if (!confirmed) {
|
|
1238
2273
|
return;
|
|
1239
2274
|
}
|
|
1240
|
-
this.
|
|
2275
|
+
this.sessionTrashClearing = true;
|
|
1241
2276
|
try {
|
|
1242
|
-
const res = await api('
|
|
1243
|
-
source: session.source,
|
|
1244
|
-
sessionId: session.sessionId,
|
|
1245
|
-
filePath: session.filePath
|
|
1246
|
-
});
|
|
2277
|
+
const res = await api('purge-session-trash', { all: true });
|
|
1247
2278
|
if (res.error) {
|
|
1248
2279
|
this.showMessage(res.error, 'error');
|
|
1249
2280
|
return;
|
|
1250
2281
|
}
|
|
1251
|
-
this.showMessage('
|
|
1252
|
-
|
|
2282
|
+
this.showMessage('回收站已清空', 'success');
|
|
2283
|
+
this.invalidateSessionTrashRequests();
|
|
2284
|
+
await this.loadSessionTrash({ forceRefresh: true });
|
|
1253
2285
|
} catch (e) {
|
|
1254
|
-
this.showMessage('
|
|
2286
|
+
this.showMessage('清空回收站失败', 'error');
|
|
1255
2287
|
} finally {
|
|
1256
|
-
this.
|
|
2288
|
+
this.sessionTrashClearing = false;
|
|
1257
2289
|
}
|
|
1258
2290
|
},
|
|
1259
2291
|
|
|
@@ -1384,6 +2416,124 @@ import { createSkillsMethods } from './modules/skills.methods.mjs';
|
|
|
1384
2416
|
localStorage.removeItem('codexmateSessionPathFilter');
|
|
1385
2417
|
}
|
|
1386
2418
|
},
|
|
2419
|
+
normalizeSessionPinnedMap(raw) {
|
|
2420
|
+
if (!raw || typeof raw !== 'object' || Array.isArray(raw)) {
|
|
2421
|
+
return {};
|
|
2422
|
+
}
|
|
2423
|
+
const next = {};
|
|
2424
|
+
for (const [key, value] of Object.entries(raw)) {
|
|
2425
|
+
if (!key) continue;
|
|
2426
|
+
const numeric = Number(value);
|
|
2427
|
+
if (!Number.isFinite(numeric) || numeric <= 0) continue;
|
|
2428
|
+
next[key] = Math.floor(numeric);
|
|
2429
|
+
}
|
|
2430
|
+
return next;
|
|
2431
|
+
},
|
|
2432
|
+
restoreSessionPinnedMap() {
|
|
2433
|
+
const cached = localStorage.getItem('codexmateSessionPinnedMap');
|
|
2434
|
+
if (!cached) {
|
|
2435
|
+
this.sessionPinnedMap = {};
|
|
2436
|
+
return;
|
|
2437
|
+
}
|
|
2438
|
+
try {
|
|
2439
|
+
const parsed = JSON.parse(cached);
|
|
2440
|
+
this.sessionPinnedMap = this.normalizeSessionPinnedMap(parsed);
|
|
2441
|
+
} catch (_) {
|
|
2442
|
+
this.sessionPinnedMap = {};
|
|
2443
|
+
localStorage.removeItem('codexmateSessionPinnedMap');
|
|
2444
|
+
}
|
|
2445
|
+
},
|
|
2446
|
+
persistSessionPinnedMap() {
|
|
2447
|
+
const payload = (this.sessionPinnedMap && typeof this.sessionPinnedMap === 'object')
|
|
2448
|
+
? this.sessionPinnedMap
|
|
2449
|
+
: {};
|
|
2450
|
+
localStorage.setItem('codexmateSessionPinnedMap', JSON.stringify(payload));
|
|
2451
|
+
},
|
|
2452
|
+
shouldPruneSessionPinnedMap(sessions = this.sessionsList) {
|
|
2453
|
+
if (!Array.isArray(sessions) || sessions.length === 0) {
|
|
2454
|
+
return false;
|
|
2455
|
+
}
|
|
2456
|
+
if (this.sessionFilterSource !== 'all') {
|
|
2457
|
+
return false;
|
|
2458
|
+
}
|
|
2459
|
+
if (this.sessionPathFilter) {
|
|
2460
|
+
return false;
|
|
2461
|
+
}
|
|
2462
|
+
if (this.sessionQuery && isSessionQueryEnabled(this.sessionFilterSource)) {
|
|
2463
|
+
return false;
|
|
2464
|
+
}
|
|
2465
|
+
if (this.sessionRoleFilter && this.sessionRoleFilter !== 'all') {
|
|
2466
|
+
return false;
|
|
2467
|
+
}
|
|
2468
|
+
if (this.sessionTimePreset && this.sessionTimePreset !== 'all') {
|
|
2469
|
+
return false;
|
|
2470
|
+
}
|
|
2471
|
+
return true;
|
|
2472
|
+
},
|
|
2473
|
+
pruneSessionPinnedMap(sessions = this.sessionsList) {
|
|
2474
|
+
const current = (this.sessionPinnedMap && typeof this.sessionPinnedMap === 'object')
|
|
2475
|
+
? this.sessionPinnedMap
|
|
2476
|
+
: {};
|
|
2477
|
+
const list = Array.isArray(sessions) ? sessions : [];
|
|
2478
|
+
if (Object.keys(current).length === 0 || !this.shouldPruneSessionPinnedMap(list)) {
|
|
2479
|
+
return;
|
|
2480
|
+
}
|
|
2481
|
+
const validKeys = new Set(list.map((session) => this.getSessionExportKey(session)).filter(Boolean));
|
|
2482
|
+
const next = {};
|
|
2483
|
+
let changed = false;
|
|
2484
|
+
for (const [key, value] of Object.entries(current)) {
|
|
2485
|
+
if (!validKeys.has(key)) {
|
|
2486
|
+
changed = true;
|
|
2487
|
+
continue;
|
|
2488
|
+
}
|
|
2489
|
+
next[key] = value;
|
|
2490
|
+
}
|
|
2491
|
+
if (!changed) {
|
|
2492
|
+
return;
|
|
2493
|
+
}
|
|
2494
|
+
this.sessionPinnedMap = next;
|
|
2495
|
+
this.persistSessionPinnedMap();
|
|
2496
|
+
},
|
|
2497
|
+
getSessionPinTimestamp(session) {
|
|
2498
|
+
if (!session) return 0;
|
|
2499
|
+
const key = this.getSessionExportKey(session);
|
|
2500
|
+
if (!key) return 0;
|
|
2501
|
+
const raw = this.sessionPinnedMap && this.sessionPinnedMap[key];
|
|
2502
|
+
const numeric = Number(raw);
|
|
2503
|
+
return Number.isFinite(numeric) && numeric > 0 ? Math.floor(numeric) : 0;
|
|
2504
|
+
},
|
|
2505
|
+
isSessionPinned(session) {
|
|
2506
|
+
return this.getSessionPinTimestamp(session) > 0;
|
|
2507
|
+
},
|
|
2508
|
+
toggleSessionPin(session) {
|
|
2509
|
+
if (!session) return;
|
|
2510
|
+
const key = this.getSessionExportKey(session);
|
|
2511
|
+
if (!key) return;
|
|
2512
|
+
const current = (this.sessionPinnedMap && typeof this.sessionPinnedMap === 'object')
|
|
2513
|
+
? this.sessionPinnedMap
|
|
2514
|
+
: {};
|
|
2515
|
+
const next = { ...current };
|
|
2516
|
+
if (next[key]) {
|
|
2517
|
+
delete next[key];
|
|
2518
|
+
} else {
|
|
2519
|
+
next[key] = Date.now();
|
|
2520
|
+
}
|
|
2521
|
+
this.sessionPinnedMap = next;
|
|
2522
|
+
this.persistSessionPinnedMap();
|
|
2523
|
+
},
|
|
2524
|
+
removeSessionPin(session) {
|
|
2525
|
+
if (!session) return;
|
|
2526
|
+
const key = this.getSessionExportKey(session);
|
|
2527
|
+
if (!key) return;
|
|
2528
|
+
const current = (this.sessionPinnedMap && typeof this.sessionPinnedMap === 'object')
|
|
2529
|
+
? this.sessionPinnedMap
|
|
2530
|
+
: {};
|
|
2531
|
+
if (!current[key]) return;
|
|
2532
|
+
const next = { ...current };
|
|
2533
|
+
delete next[key];
|
|
2534
|
+
this.sessionPinnedMap = next;
|
|
2535
|
+
this.persistSessionPinnedMap();
|
|
2536
|
+
},
|
|
1387
2537
|
|
|
1388
2538
|
async onSessionSourceChange() {
|
|
1389
2539
|
this.refreshSessionPathOptions(this.sessionFilterSource);
|
|
@@ -1434,13 +2584,137 @@ import { createSkillsMethods } from './modules/skills.methods.mjs';
|
|
|
1434
2584
|
},
|
|
1435
2585
|
setSessionPreviewScrollRef(el) {
|
|
1436
2586
|
this.sessionPreviewScrollEl = el || null;
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
2587
|
+
this.invalidateSessionTimelineMeasurementCache();
|
|
2588
|
+
const shouldSync = !!(
|
|
2589
|
+
this.sessionPreviewScrollEl
|
|
2590
|
+
&& this.mainTab === 'sessions'
|
|
2591
|
+
&& this.getMainTabForNav() === 'sessions'
|
|
2592
|
+
&& this.sessionPreviewRenderEnabled
|
|
2593
|
+
&& this.sessionTimelineNodes.length
|
|
2594
|
+
);
|
|
2595
|
+
if (!shouldSync) {
|
|
1440
2596
|
this.cancelSessionTimelineSync();
|
|
2597
|
+
this.updateSessionTimelineOffset();
|
|
2598
|
+
return;
|
|
1441
2599
|
}
|
|
2600
|
+
const boundScrollEl = this.sessionPreviewScrollEl;
|
|
2601
|
+
this.$nextTick(() => {
|
|
2602
|
+
if (this.sessionPreviewScrollEl !== boundScrollEl) return;
|
|
2603
|
+
if (this.mainTab !== 'sessions' || !this.sessionPreviewRenderEnabled) return;
|
|
2604
|
+
if (!this.sessionTimelineNodes.length) return;
|
|
2605
|
+
this.scheduleSessionTimelineSync();
|
|
2606
|
+
});
|
|
1442
2607
|
this.updateSessionTimelineOffset();
|
|
1443
2608
|
},
|
|
2609
|
+
clearSessionTimelineRefs() {
|
|
2610
|
+
this.sessionMessageRefMap = Object.create(null);
|
|
2611
|
+
this.sessionMessageRefBinderMap = Object.create(null);
|
|
2612
|
+
this.sessionTimelineLastAnchorY = 0;
|
|
2613
|
+
this.sessionTimelineLastDirection = 0;
|
|
2614
|
+
this.invalidateSessionTimelineMeasurementCache(true);
|
|
2615
|
+
},
|
|
2616
|
+
ensureSessionTimelineMeasurementCache() {
|
|
2617
|
+
if (this.__sessionTimelineMeasurementCache) {
|
|
2618
|
+
return this.__sessionTimelineMeasurementCache;
|
|
2619
|
+
}
|
|
2620
|
+
this.__sessionTimelineMeasurementCache = {
|
|
2621
|
+
offsetByKey: Object.create(null),
|
|
2622
|
+
dirty: true
|
|
2623
|
+
};
|
|
2624
|
+
return this.__sessionTimelineMeasurementCache;
|
|
2625
|
+
},
|
|
2626
|
+
invalidateSessionTimelineMeasurementCache(resetOffset = false) {
|
|
2627
|
+
const cache = this.ensureSessionTimelineMeasurementCache();
|
|
2628
|
+
if (resetOffset) {
|
|
2629
|
+
cache.offsetByKey = Object.create(null);
|
|
2630
|
+
}
|
|
2631
|
+
cache.dirty = true;
|
|
2632
|
+
},
|
|
2633
|
+
refreshSessionTimelineMeasurementCache(nodes = null) {
|
|
2634
|
+
const cache = this.ensureSessionTimelineMeasurementCache();
|
|
2635
|
+
const nodeList = Array.isArray(nodes) ? nodes : (Array.isArray(this.sessionTimelineNodes) ? this.sessionTimelineNodes : []);
|
|
2636
|
+
if (!nodeList.length) {
|
|
2637
|
+
cache.offsetByKey = Object.create(null);
|
|
2638
|
+
cache.dirty = false;
|
|
2639
|
+
return cache.offsetByKey;
|
|
2640
|
+
}
|
|
2641
|
+
const scrollEl = this.sessionPreviewScrollEl || this.$refs.sessionPreviewScroll;
|
|
2642
|
+
const scrollRect = scrollEl && typeof scrollEl.getBoundingClientRect === 'function'
|
|
2643
|
+
? scrollEl.getBoundingClientRect()
|
|
2644
|
+
: null;
|
|
2645
|
+
const scrollTop = scrollEl ? Number(scrollEl.scrollTop || 0) : 0;
|
|
2646
|
+
const nextOffsetByKey = Object.create(null);
|
|
2647
|
+
for (const node of nodeList) {
|
|
2648
|
+
if (!node || !node.key) continue;
|
|
2649
|
+
const messageEl = this.sessionMessageRefMap[node.key];
|
|
2650
|
+
if (!messageEl) continue;
|
|
2651
|
+
let top = Number.NaN;
|
|
2652
|
+
if (
|
|
2653
|
+
scrollRect
|
|
2654
|
+
&& typeof messageEl.getBoundingClientRect === 'function'
|
|
2655
|
+
) {
|
|
2656
|
+
const messageRect = messageEl.getBoundingClientRect();
|
|
2657
|
+
top = scrollTop + (messageRect.top - scrollRect.top);
|
|
2658
|
+
} else {
|
|
2659
|
+
top = Number(messageEl.offsetTop || 0);
|
|
2660
|
+
}
|
|
2661
|
+
if (!Number.isFinite(top)) continue;
|
|
2662
|
+
nextOffsetByKey[node.key] = top;
|
|
2663
|
+
}
|
|
2664
|
+
cache.offsetByKey = nextOffsetByKey;
|
|
2665
|
+
cache.dirty = false;
|
|
2666
|
+
return cache.offsetByKey;
|
|
2667
|
+
},
|
|
2668
|
+
getCachedSessionTimelineMeasuredNodes(nodes) {
|
|
2669
|
+
const nodeList = Array.isArray(nodes) ? nodes : [];
|
|
2670
|
+
if (!nodeList.length) {
|
|
2671
|
+
return [];
|
|
2672
|
+
}
|
|
2673
|
+
const cache = this.ensureSessionTimelineMeasurementCache();
|
|
2674
|
+
if (cache.dirty) {
|
|
2675
|
+
this.refreshSessionTimelineMeasurementCache(nodeList);
|
|
2676
|
+
}
|
|
2677
|
+
const offsetByKey = cache.offsetByKey || Object.create(null);
|
|
2678
|
+
const measuredNodes = [];
|
|
2679
|
+
for (const node of nodeList) {
|
|
2680
|
+
if (!node || !node.key) continue;
|
|
2681
|
+
const top = Number(offsetByKey[node.key]);
|
|
2682
|
+
if (!Number.isFinite(top)) continue;
|
|
2683
|
+
measuredNodes.push({
|
|
2684
|
+
key: node.key,
|
|
2685
|
+
top
|
|
2686
|
+
});
|
|
2687
|
+
}
|
|
2688
|
+
if (measuredNodes.length >= nodeList.length) {
|
|
2689
|
+
return measuredNodes;
|
|
2690
|
+
}
|
|
2691
|
+
const refreshedOffsetByKey = this.refreshSessionTimelineMeasurementCache(nodeList);
|
|
2692
|
+
const refreshedNodes = [];
|
|
2693
|
+
for (const node of nodeList) {
|
|
2694
|
+
if (!node || !node.key) continue;
|
|
2695
|
+
const top = Number(refreshedOffsetByKey[node.key]);
|
|
2696
|
+
if (!Number.isFinite(top)) continue;
|
|
2697
|
+
refreshedNodes.push({
|
|
2698
|
+
key: node.key,
|
|
2699
|
+
top
|
|
2700
|
+
});
|
|
2701
|
+
}
|
|
2702
|
+
return refreshedNodes;
|
|
2703
|
+
},
|
|
2704
|
+
getSessionMessageRefBinder(messageKey) {
|
|
2705
|
+
if (!this.isSessionTimelineNodeKey(messageKey)) return null;
|
|
2706
|
+
const current = this.sessionMessageRefBinderMap[messageKey];
|
|
2707
|
+
if (!current || current.ticket !== this.sessionTabRenderTicket) {
|
|
2708
|
+
const ticket = this.sessionTabRenderTicket;
|
|
2709
|
+
this.sessionMessageRefBinderMap[messageKey] = {
|
|
2710
|
+
ticket,
|
|
2711
|
+
bind: (el) => {
|
|
2712
|
+
this.bindSessionMessageRef(messageKey, el, ticket);
|
|
2713
|
+
}
|
|
2714
|
+
};
|
|
2715
|
+
}
|
|
2716
|
+
return this.sessionMessageRefBinderMap[messageKey].bind;
|
|
2717
|
+
},
|
|
1444
2718
|
updateSessionTimelineOffset() {
|
|
1445
2719
|
const container = this.sessionPreviewContainerEl || this.$refs.sessionPreviewContainer;
|
|
1446
2720
|
if (!container || !container.style) return;
|
|
@@ -1451,12 +2725,39 @@ import { createSkillsMethods } from './modules/skills.methods.mjs';
|
|
|
1451
2725
|
const offset = headerHeight > 0 ? (headerHeight + 12) : 72;
|
|
1452
2726
|
container.style.setProperty('--session-preview-header-offset', `${offset}px`);
|
|
1453
2727
|
},
|
|
1454
|
-
bindSessionMessageRef(messageKey, el) {
|
|
2728
|
+
bindSessionMessageRef(messageKey, el, ticket = this.sessionTabRenderTicket) {
|
|
2729
|
+
if (!this.sessionTimelineEnabled) return;
|
|
1455
2730
|
if (!messageKey) return;
|
|
2731
|
+
if (ticket !== this.sessionTabRenderTicket) return;
|
|
1456
2732
|
if (el) {
|
|
2733
|
+
if (!this.isSessionTimelineNodeKey(messageKey)) return;
|
|
2734
|
+
if (this.sessionMessageRefMap[messageKey] === el) return;
|
|
1457
2735
|
this.sessionMessageRefMap[messageKey] = el;
|
|
2736
|
+
this.invalidateSessionTimelineMeasurementCache();
|
|
1458
2737
|
} else {
|
|
2738
|
+
if (!this.sessionMessageRefMap[messageKey]) return;
|
|
1459
2739
|
delete this.sessionMessageRefMap[messageKey];
|
|
2740
|
+
this.invalidateSessionTimelineMeasurementCache();
|
|
2741
|
+
}
|
|
2742
|
+
},
|
|
2743
|
+
isSessionTimelineNodeKey(messageKey) {
|
|
2744
|
+
if (!messageKey) return false;
|
|
2745
|
+
return !!(this.sessionTimelineNodeKeyMap && this.sessionTimelineNodeKeyMap[messageKey]);
|
|
2746
|
+
},
|
|
2747
|
+
pruneSessionMessageRefs() {
|
|
2748
|
+
const nodeKeyMap = this.sessionTimelineNodeKeyMap || Object.create(null);
|
|
2749
|
+
let removed = false;
|
|
2750
|
+
for (const key of Object.keys(this.sessionMessageRefMap)) {
|
|
2751
|
+
if (nodeKeyMap[key]) continue;
|
|
2752
|
+
delete this.sessionMessageRefMap[key];
|
|
2753
|
+
removed = true;
|
|
2754
|
+
}
|
|
2755
|
+
for (const key of Object.keys(this.sessionMessageRefBinderMap)) {
|
|
2756
|
+
if (nodeKeyMap[key]) continue;
|
|
2757
|
+
delete this.sessionMessageRefBinderMap[key];
|
|
2758
|
+
}
|
|
2759
|
+
if (removed) {
|
|
2760
|
+
this.invalidateSessionTimelineMeasurementCache();
|
|
1460
2761
|
}
|
|
1461
2762
|
},
|
|
1462
2763
|
cancelSessionTimelineSync() {
|
|
@@ -1478,11 +2779,39 @@ import { createSkillsMethods } from './modules/skills.methods.mjs';
|
|
|
1478
2779
|
this.syncSessionTimelineActiveFromScroll();
|
|
1479
2780
|
},
|
|
1480
2781
|
onSessionPreviewScroll() {
|
|
2782
|
+
if (
|
|
2783
|
+
!this.sessionTimelineEnabled
|
|
2784
|
+
|| this.mainTab !== 'sessions'
|
|
2785
|
+
|| this.getMainTabForNav() !== 'sessions'
|
|
2786
|
+
|| !this.sessionPreviewRenderEnabled
|
|
2787
|
+
) return;
|
|
2788
|
+
if (!this.sessionTimelineNodes.length) return;
|
|
2789
|
+
const scrollEl = this.sessionPreviewScrollEl || this.$refs.sessionPreviewScroll;
|
|
2790
|
+
if (!scrollEl) return;
|
|
2791
|
+
const now = Date.now();
|
|
2792
|
+
const currentTop = Number(scrollEl.scrollTop || 0);
|
|
2793
|
+
const delta = Math.abs(currentTop - Number(this.sessionTimelineLastScrollTop || 0));
|
|
2794
|
+
const elapsed = now - Number(this.sessionTimelineLastSyncAt || 0);
|
|
2795
|
+
if (delta < 48 && elapsed < 120) {
|
|
2796
|
+
return;
|
|
2797
|
+
}
|
|
2798
|
+
this.sessionTimelineLastScrollTop = currentTop;
|
|
2799
|
+
this.sessionTimelineLastSyncAt = now;
|
|
1481
2800
|
this.scheduleSessionTimelineSync();
|
|
1482
2801
|
},
|
|
1483
2802
|
onWindowResize() {
|
|
1484
2803
|
this.updateCompactLayoutMode();
|
|
2804
|
+
if (
|
|
2805
|
+
!this.sessionTimelineEnabled
|
|
2806
|
+
|| this.mainTab !== 'sessions'
|
|
2807
|
+
|| this.getMainTabForNav() !== 'sessions'
|
|
2808
|
+
|| !this.sessionPreviewRenderEnabled
|
|
2809
|
+
) {
|
|
2810
|
+
return;
|
|
2811
|
+
}
|
|
2812
|
+
if (!this.sessionTimelineNodes.length) return;
|
|
1485
2813
|
this.updateSessionTimelineOffset();
|
|
2814
|
+
this.invalidateSessionTimelineMeasurementCache();
|
|
1486
2815
|
this.scheduleSessionTimelineSync();
|
|
1487
2816
|
},
|
|
1488
2817
|
shouldForceCompactLayout() {
|
|
@@ -1535,35 +2864,93 @@ import { createSkillsMethods } from './modules/skills.methods.mjs';
|
|
|
1535
2864
|
this.applyCompactLayoutClass(enabled);
|
|
1536
2865
|
},
|
|
1537
2866
|
syncSessionTimelineActiveFromScroll() {
|
|
2867
|
+
if (
|
|
2868
|
+
!this.sessionTimelineEnabled
|
|
2869
|
+
|| this.mainTab !== 'sessions'
|
|
2870
|
+
|| this.getMainTabForNav() !== 'sessions'
|
|
2871
|
+
|| !this.sessionPreviewRenderEnabled
|
|
2872
|
+
) {
|
|
2873
|
+
if (this.sessionTimelineActiveKey) {
|
|
2874
|
+
this.sessionTimelineActiveKey = '';
|
|
2875
|
+
}
|
|
2876
|
+
return;
|
|
2877
|
+
}
|
|
1538
2878
|
const nodes = Array.isArray(this.sessionTimelineNodes) ? this.sessionTimelineNodes : [];
|
|
1539
2879
|
if (!nodes.length) {
|
|
1540
|
-
this.sessionTimelineActiveKey
|
|
2880
|
+
if (this.sessionTimelineActiveKey) {
|
|
2881
|
+
this.sessionTimelineActiveKey = '';
|
|
2882
|
+
}
|
|
1541
2883
|
return;
|
|
1542
2884
|
}
|
|
2885
|
+
this.pruneSessionMessageRefs();
|
|
1543
2886
|
const scrollEl = this.sessionPreviewScrollEl || this.$refs.sessionPreviewScroll;
|
|
1544
2887
|
if (!scrollEl) {
|
|
1545
|
-
this.sessionTimelineActiveKey
|
|
2888
|
+
if (!this.isSessionTimelineNodeKey(this.sessionTimelineActiveKey)) {
|
|
2889
|
+
const fallbackKey = nodes[0].key;
|
|
2890
|
+
if (this.sessionTimelineActiveKey !== fallbackKey) {
|
|
2891
|
+
this.sessionTimelineActiveKey = fallbackKey;
|
|
2892
|
+
}
|
|
2893
|
+
}
|
|
1546
2894
|
return;
|
|
1547
2895
|
}
|
|
1548
|
-
const scrollRect = scrollEl.getBoundingClientRect();
|
|
1549
2896
|
const headerEl = scrollEl.querySelector('.session-preview-header');
|
|
1550
|
-
const
|
|
1551
|
-
const
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
2897
|
+
const stickyOffset = headerEl ? (headerEl.offsetHeight + 8) : 8;
|
|
2898
|
+
const rawAnchorY = Number(scrollEl.scrollTop || 0) + stickyOffset;
|
|
2899
|
+
const previousAnchorY = Number(this.sessionTimelineLastAnchorY || 0);
|
|
2900
|
+
let direction = rawAnchorY - previousAnchorY;
|
|
2901
|
+
if (Math.abs(direction) < 1) {
|
|
2902
|
+
direction = Number(this.sessionTimelineLastDirection || 0);
|
|
2903
|
+
} else {
|
|
2904
|
+
this.sessionTimelineLastDirection = direction > 0 ? 1 : -1;
|
|
2905
|
+
}
|
|
2906
|
+
this.sessionTimelineLastAnchorY = rawAnchorY;
|
|
2907
|
+
const hysteresisPx = 18;
|
|
2908
|
+
const hysteresis = direction > 0 ? -hysteresisPx : (direction < 0 ? hysteresisPx : 0);
|
|
2909
|
+
const anchorY = rawAnchorY + hysteresis;
|
|
2910
|
+
const measuredNodes = this.getCachedSessionTimelineMeasuredNodes(nodes);
|
|
2911
|
+
if (!measuredNodes.length) {
|
|
2912
|
+
if (!this.isSessionTimelineNodeKey(this.sessionTimelineActiveKey)) {
|
|
2913
|
+
this.sessionTimelineActiveKey = nodes[0].key;
|
|
2914
|
+
}
|
|
2915
|
+
return;
|
|
2916
|
+
}
|
|
2917
|
+
let low = 0;
|
|
2918
|
+
let high = measuredNodes.length - 1;
|
|
2919
|
+
let candidateIndex = 0;
|
|
2920
|
+
while (low <= high) {
|
|
2921
|
+
const mid = Math.floor((low + high) / 2);
|
|
2922
|
+
if (measuredNodes[mid].top <= anchorY) {
|
|
2923
|
+
candidateIndex = mid;
|
|
2924
|
+
low = mid + 1;
|
|
2925
|
+
} else {
|
|
2926
|
+
high = mid - 1;
|
|
2927
|
+
}
|
|
2928
|
+
}
|
|
2929
|
+
let currentIndex = -1;
|
|
2930
|
+
if (this.sessionTimelineActiveKey) {
|
|
2931
|
+
for (let i = 0; i < measuredNodes.length; i += 1) {
|
|
2932
|
+
if (measuredNodes[i].key === this.sessionTimelineActiveKey) {
|
|
2933
|
+
currentIndex = i;
|
|
2934
|
+
break;
|
|
2935
|
+
}
|
|
2936
|
+
}
|
|
2937
|
+
}
|
|
2938
|
+
if (currentIndex >= 0) {
|
|
2939
|
+
if (direction > 0 && candidateIndex < currentIndex) {
|
|
2940
|
+
candidateIndex = currentIndex;
|
|
2941
|
+
} else if (direction < 0 && candidateIndex > currentIndex) {
|
|
2942
|
+
candidateIndex = currentIndex;
|
|
1560
2943
|
}
|
|
1561
|
-
break;
|
|
1562
2944
|
}
|
|
1563
|
-
|
|
2945
|
+
const activeKey = measuredNodes[candidateIndex].key;
|
|
2946
|
+
if (this.sessionTimelineActiveKey !== activeKey) {
|
|
2947
|
+
this.sessionTimelineActiveKey = activeKey;
|
|
2948
|
+
}
|
|
1564
2949
|
},
|
|
1565
2950
|
jumpToSessionTimelineNode(messageKey) {
|
|
2951
|
+
if (!this.sessionTimelineEnabled || this.mainTab !== 'sessions' || !this.sessionPreviewRenderEnabled) return;
|
|
1566
2952
|
if (!messageKey) return;
|
|
2953
|
+
if (!this.isSessionTimelineNodeKey(messageKey)) return;
|
|
1567
2954
|
const scrollEl = this.sessionPreviewScrollEl || this.$refs.sessionPreviewScroll;
|
|
1568
2955
|
if (!scrollEl) return;
|
|
1569
2956
|
const messageEl = this.sessionMessageRefMap[messageKey];
|
|
@@ -1636,67 +3023,9 @@ import { createSkillsMethods } from './modules/skills.methods.mjs';
|
|
|
1636
3023
|
},
|
|
1637
3024
|
|
|
1638
3025
|
async loadSessions() {
|
|
1639
|
-
|
|
1640
|
-
this.
|
|
1641
|
-
|
|
1642
|
-
const params = buildSessionListParams({
|
|
1643
|
-
source: this.sessionFilterSource,
|
|
1644
|
-
pathFilter: this.sessionPathFilter,
|
|
1645
|
-
query: this.sessionQuery,
|
|
1646
|
-
roleFilter: this.sessionRoleFilter,
|
|
1647
|
-
timeRangePreset: this.sessionTimePreset
|
|
1648
|
-
});
|
|
1649
|
-
try {
|
|
1650
|
-
const res = await api('list-sessions', params);
|
|
1651
|
-
if (res.error) {
|
|
1652
|
-
this.showMessage(res.error, 'error');
|
|
1653
|
-
this.sessionsList = [];
|
|
1654
|
-
this.activeSession = null;
|
|
1655
|
-
this.activeSessionMessages = [];
|
|
1656
|
-
this.activeSessionDetailClipped = false;
|
|
1657
|
-
this.cancelSessionTimelineSync();
|
|
1658
|
-
this.sessionTimelineActiveKey = '';
|
|
1659
|
-
this.sessionMessageRefMap = Object.create(null);
|
|
1660
|
-
} else {
|
|
1661
|
-
this.sessionsList = Array.isArray(res.sessions) ? res.sessions : [];
|
|
1662
|
-
this.syncSessionPathOptionsForSource(
|
|
1663
|
-
this.sessionFilterSource,
|
|
1664
|
-
this.extractPathOptionsFromSessions(this.sessionsList),
|
|
1665
|
-
true
|
|
1666
|
-
);
|
|
1667
|
-
if (this.sessionsList.length === 0) {
|
|
1668
|
-
this.activeSession = null;
|
|
1669
|
-
this.activeSessionMessages = [];
|
|
1670
|
-
this.activeSessionDetailClipped = false;
|
|
1671
|
-
this.cancelSessionTimelineSync();
|
|
1672
|
-
this.sessionTimelineActiveKey = '';
|
|
1673
|
-
this.sessionMessageRefMap = Object.create(null);
|
|
1674
|
-
} else {
|
|
1675
|
-
const oldKey = this.activeSession ? this.getSessionExportKey(this.activeSession) : '';
|
|
1676
|
-
const matched = this.sessionsList.find(item => this.getSessionExportKey(item) === oldKey);
|
|
1677
|
-
this.activeSession = matched || this.sessionsList[0];
|
|
1678
|
-
this.activeSessionMessages = [];
|
|
1679
|
-
this.activeSessionDetailError = '';
|
|
1680
|
-
this.activeSessionDetailClipped = false;
|
|
1681
|
-
this.cancelSessionTimelineSync();
|
|
1682
|
-
this.sessionTimelineActiveKey = '';
|
|
1683
|
-
this.sessionMessageRefMap = Object.create(null);
|
|
1684
|
-
await this.loadActiveSessionDetail();
|
|
1685
|
-
}
|
|
1686
|
-
void this.loadSessionPathOptions({ source: this.sessionFilterSource });
|
|
1687
|
-
}
|
|
1688
|
-
} catch (e) {
|
|
1689
|
-
this.sessionsList = [];
|
|
1690
|
-
this.activeSession = null;
|
|
1691
|
-
this.activeSessionMessages = [];
|
|
1692
|
-
this.activeSessionDetailClipped = false;
|
|
1693
|
-
this.cancelSessionTimelineSync();
|
|
1694
|
-
this.sessionTimelineActiveKey = '';
|
|
1695
|
-
this.sessionMessageRefMap = Object.create(null);
|
|
1696
|
-
this.showMessage('加载会话失败', 'error');
|
|
1697
|
-
} finally {
|
|
1698
|
-
this.sessionsLoading = false;
|
|
1699
|
-
}
|
|
3026
|
+
const result = await loadSessionsHelper.call(this, api);
|
|
3027
|
+
this.pruneSessionPinnedMap(this.sessionsList);
|
|
3028
|
+
return result;
|
|
1700
3029
|
},
|
|
1701
3030
|
|
|
1702
3031
|
async selectSession(session) {
|
|
@@ -1704,11 +3033,13 @@ import { createSkillsMethods } from './modules/skills.methods.mjs';
|
|
|
1704
3033
|
if (this.activeSession && this.getSessionExportKey(this.activeSession) === this.getSessionExportKey(session)) return;
|
|
1705
3034
|
this.activeSession = session;
|
|
1706
3035
|
this.activeSessionMessages = [];
|
|
3036
|
+
this.resetSessionDetailPagination();
|
|
3037
|
+
this.resetSessionPreviewMessageRender();
|
|
1707
3038
|
this.activeSessionDetailError = '';
|
|
1708
3039
|
this.activeSessionDetailClipped = false;
|
|
1709
3040
|
this.cancelSessionTimelineSync();
|
|
1710
3041
|
this.sessionTimelineActiveKey = '';
|
|
1711
|
-
this.
|
|
3042
|
+
this.clearSessionTimelineRefs();
|
|
1712
3043
|
await this.loadActiveSessionDetail();
|
|
1713
3044
|
},
|
|
1714
3045
|
|
|
@@ -1757,87 +3088,8 @@ import { createSkillsMethods } from './modules/skills.methods.mjs';
|
|
|
1757
3088
|
}
|
|
1758
3089
|
},
|
|
1759
3090
|
|
|
1760
|
-
async loadActiveSessionDetail() {
|
|
1761
|
-
|
|
1762
|
-
this.activeSessionMessages = [];
|
|
1763
|
-
this.activeSessionDetailError = '';
|
|
1764
|
-
this.activeSessionDetailClipped = false;
|
|
1765
|
-
this.cancelSessionTimelineSync();
|
|
1766
|
-
this.sessionTimelineActiveKey = '';
|
|
1767
|
-
this.sessionMessageRefMap = Object.create(null);
|
|
1768
|
-
return;
|
|
1769
|
-
}
|
|
1770
|
-
|
|
1771
|
-
const requestSeq = ++this.sessionDetailRequestSeq;
|
|
1772
|
-
this.sessionDetailLoading = true;
|
|
1773
|
-
this.activeSessionDetailError = '';
|
|
1774
|
-
try {
|
|
1775
|
-
const res = await api('session-detail', {
|
|
1776
|
-
source: this.activeSession.source,
|
|
1777
|
-
sessionId: this.activeSession.sessionId,
|
|
1778
|
-
filePath: this.activeSession.filePath,
|
|
1779
|
-
messageLimit: 300
|
|
1780
|
-
});
|
|
1781
|
-
|
|
1782
|
-
if (requestSeq !== this.sessionDetailRequestSeq) {
|
|
1783
|
-
return;
|
|
1784
|
-
}
|
|
1785
|
-
|
|
1786
|
-
if (res.error) {
|
|
1787
|
-
this.activeSessionMessages = [];
|
|
1788
|
-
this.activeSessionDetailClipped = false;
|
|
1789
|
-
this.activeSessionDetailError = res.error;
|
|
1790
|
-
this.cancelSessionTimelineSync();
|
|
1791
|
-
this.sessionTimelineActiveKey = '';
|
|
1792
|
-
this.sessionMessageRefMap = Object.create(null);
|
|
1793
|
-
return;
|
|
1794
|
-
}
|
|
1795
|
-
|
|
1796
|
-
const rawMessages = Array.isArray(res.messages) ? res.messages : [];
|
|
1797
|
-
this.activeSessionMessages = rawMessages.map((message) => this.normalizeSessionMessage(message));
|
|
1798
|
-
this.activeSessionDetailClipped = !!res.clipped;
|
|
1799
|
-
if (this.activeSession) {
|
|
1800
|
-
if (res.sourceLabel) {
|
|
1801
|
-
this.activeSession.sourceLabel = res.sourceLabel;
|
|
1802
|
-
}
|
|
1803
|
-
if (res.sessionId) {
|
|
1804
|
-
this.activeSession.sessionId = res.sessionId;
|
|
1805
|
-
if (!this.activeSession.title) {
|
|
1806
|
-
this.activeSession.title = res.sessionId;
|
|
1807
|
-
}
|
|
1808
|
-
}
|
|
1809
|
-
if (res.filePath) {
|
|
1810
|
-
this.activeSession.filePath = res.filePath;
|
|
1811
|
-
}
|
|
1812
|
-
}
|
|
1813
|
-
if (res.updatedAt) {
|
|
1814
|
-
this.activeSession.updatedAt = res.updatedAt;
|
|
1815
|
-
}
|
|
1816
|
-
if (res.cwd) {
|
|
1817
|
-
this.activeSession.cwd = res.cwd;
|
|
1818
|
-
}
|
|
1819
|
-
if (Number.isFinite(res.totalMessages)) {
|
|
1820
|
-
this.syncActiveSessionMessageCount(res.totalMessages);
|
|
1821
|
-
}
|
|
1822
|
-
this.$nextTick(() => {
|
|
1823
|
-
this.updateSessionTimelineOffset();
|
|
1824
|
-
this.scheduleSessionTimelineSync();
|
|
1825
|
-
});
|
|
1826
|
-
} catch (e) {
|
|
1827
|
-
if (requestSeq !== this.sessionDetailRequestSeq) {
|
|
1828
|
-
return;
|
|
1829
|
-
}
|
|
1830
|
-
this.activeSessionMessages = [];
|
|
1831
|
-
this.activeSessionDetailClipped = false;
|
|
1832
|
-
this.activeSessionDetailError = '加载会话内容失败: ' + e.message;
|
|
1833
|
-
this.cancelSessionTimelineSync();
|
|
1834
|
-
this.sessionTimelineActiveKey = '';
|
|
1835
|
-
this.sessionMessageRefMap = Object.create(null);
|
|
1836
|
-
} finally {
|
|
1837
|
-
if (requestSeq === this.sessionDetailRequestSeq) {
|
|
1838
|
-
this.sessionDetailLoading = false;
|
|
1839
|
-
}
|
|
1840
|
-
}
|
|
3091
|
+
async loadActiveSessionDetail(options = {}) {
|
|
3092
|
+
return loadActiveSessionDetailHelper.call(this, api, options);
|
|
1841
3093
|
},
|
|
1842
3094
|
|
|
1843
3095
|
downloadTextFile(fileName, content, mimeType = 'text/markdown;charset=utf-8') {
|
|
@@ -2142,9 +3394,11 @@ import { createSkillsMethods } from './modules/skills.methods.mjs';
|
|
|
2142
3394
|
return;
|
|
2143
3395
|
}
|
|
2144
3396
|
this.agentsContent = res.content || '';
|
|
3397
|
+
this.agentsOriginalContent = this.agentsContent;
|
|
2145
3398
|
this.agentsPath = res.path || '';
|
|
2146
3399
|
this.agentsExists = !!res.exists;
|
|
2147
3400
|
this.agentsLineEnding = res.lineEnding === '\r\n' ? '\r\n' : '\n';
|
|
3401
|
+
this.resetAgentsDiffState();
|
|
2148
3402
|
this.showAgentsModal = true;
|
|
2149
3403
|
} catch (e) {
|
|
2150
3404
|
this.showMessage('加载文件失败', 'error');
|
|
@@ -2168,9 +3422,11 @@ import { createSkillsMethods } from './modules/skills.methods.mjs';
|
|
|
2168
3422
|
this.showMessage(`OpenClaw 配置解析失败,已使用默认 Workspace:${res.configError}`, 'error');
|
|
2169
3423
|
}
|
|
2170
3424
|
this.agentsContent = res.content || '';
|
|
3425
|
+
this.agentsOriginalContent = this.agentsContent;
|
|
2171
3426
|
this.agentsPath = res.path || '';
|
|
2172
3427
|
this.agentsExists = !!res.exists;
|
|
2173
3428
|
this.agentsLineEnding = res.lineEnding === '\r\n' ? '\r\n' : '\n';
|
|
3429
|
+
this.resetAgentsDiffState();
|
|
2174
3430
|
this.showAgentsModal = true;
|
|
2175
3431
|
} catch (e) {
|
|
2176
3432
|
this.showMessage('加载文件失败', 'error');
|
|
@@ -2197,9 +3453,11 @@ import { createSkillsMethods } from './modules/skills.methods.mjs';
|
|
|
2197
3453
|
this.showMessage(`OpenClaw 配置解析失败,已使用默认 Workspace:${res.configError}`, 'error');
|
|
2198
3454
|
}
|
|
2199
3455
|
this.agentsContent = res.content || '';
|
|
3456
|
+
this.agentsOriginalContent = this.agentsContent;
|
|
2200
3457
|
this.agentsPath = res.path || '';
|
|
2201
3458
|
this.agentsExists = !!res.exists;
|
|
2202
3459
|
this.agentsLineEnding = res.lineEnding === '\r\n' ? '\r\n' : '\n';
|
|
3460
|
+
this.resetAgentsDiffState();
|
|
2203
3461
|
this.showAgentsModal = true;
|
|
2204
3462
|
} catch (e) {
|
|
2205
3463
|
this.showMessage('加载文件失败', 'error');
|
|
@@ -2228,18 +3486,272 @@ import { createSkillsMethods } from './modules/skills.methods.mjs';
|
|
|
2228
3486
|
this.agentsWorkspaceFileName = '';
|
|
2229
3487
|
},
|
|
2230
3488
|
|
|
2231
|
-
|
|
3489
|
+
resetAgentsDiffState() {
|
|
3490
|
+
this.agentsDiffVisible = false;
|
|
3491
|
+
this.agentsDiffLoading = false;
|
|
3492
|
+
this.agentsDiffError = '';
|
|
3493
|
+
this.agentsDiffLines = [];
|
|
3494
|
+
this.agentsDiffStats = {
|
|
3495
|
+
added: 0,
|
|
3496
|
+
removed: 0,
|
|
3497
|
+
unchanged: 0
|
|
3498
|
+
};
|
|
3499
|
+
this.agentsDiffTruncated = false;
|
|
3500
|
+
this.agentsDiffHasChangesValue = false;
|
|
3501
|
+
this.agentsDiffFingerprint = '';
|
|
3502
|
+
this._agentsDiffPreviewRequestToken = null;
|
|
3503
|
+
},
|
|
3504
|
+
handleGlobalKeydown(event) {
|
|
3505
|
+
if (!event || event.key !== 'Escape') {
|
|
3506
|
+
return;
|
|
3507
|
+
}
|
|
3508
|
+
if (this.showConfirmDialog) {
|
|
3509
|
+
event.preventDefault();
|
|
3510
|
+
event.stopPropagation();
|
|
3511
|
+
this.resolveConfirmDialog(false);
|
|
3512
|
+
return;
|
|
3513
|
+
}
|
|
3514
|
+
if (!this.showAgentsModal) {
|
|
3515
|
+
return;
|
|
3516
|
+
}
|
|
3517
|
+
event.preventDefault();
|
|
3518
|
+
event.stopPropagation();
|
|
3519
|
+
if (this.agentsSaving || this.agentsDiffLoading) {
|
|
3520
|
+
return;
|
|
3521
|
+
}
|
|
3522
|
+
if (this.agentsDiffVisible) {
|
|
3523
|
+
this.resetAgentsDiffState();
|
|
3524
|
+
return;
|
|
3525
|
+
}
|
|
3526
|
+
this.closeAgentsModal();
|
|
3527
|
+
},
|
|
3528
|
+
hasPendingAgentsDraft() {
|
|
3529
|
+
if (!this.showAgentsModal || this.agentsLoading || this.agentsSaving) {
|
|
3530
|
+
return false;
|
|
3531
|
+
}
|
|
3532
|
+
return this.hasAgentsContentChanged() || this.agentsDiffVisible;
|
|
3533
|
+
},
|
|
3534
|
+
handleBeforeUnload(event) {
|
|
3535
|
+
if (!this.hasPendingAgentsDraft()) {
|
|
3536
|
+
return;
|
|
3537
|
+
}
|
|
3538
|
+
if (event && typeof event.preventDefault === 'function') {
|
|
3539
|
+
event.preventDefault();
|
|
3540
|
+
event.returnValue = '';
|
|
3541
|
+
}
|
|
3542
|
+
return '';
|
|
3543
|
+
},
|
|
3544
|
+
hasAgentsContentChanged() {
|
|
3545
|
+
const original = typeof this.agentsOriginalContent === 'string' ? this.agentsOriginalContent : '';
|
|
3546
|
+
const current = typeof this.agentsContent === 'string' ? this.agentsContent : '';
|
|
3547
|
+
return original !== current;
|
|
3548
|
+
},
|
|
3549
|
+
requestConfirmDialog(options = {}) {
|
|
3550
|
+
if (typeof this.confirmDialogResolver === 'function') {
|
|
3551
|
+
this.confirmDialogResolver(false);
|
|
3552
|
+
}
|
|
3553
|
+
this.confirmDialogTitle = typeof options.title === 'string' && options.title.trim()
|
|
3554
|
+
? options.title.trim()
|
|
3555
|
+
: '请确认操作';
|
|
3556
|
+
this.confirmDialogMessage = typeof options.message === 'string' ? options.message : '';
|
|
3557
|
+
this.confirmDialogConfirmText = typeof options.confirmText === 'string' && options.confirmText.trim()
|
|
3558
|
+
? options.confirmText.trim()
|
|
3559
|
+
: '确认';
|
|
3560
|
+
this.confirmDialogCancelText = typeof options.cancelText === 'string' && options.cancelText.trim()
|
|
3561
|
+
? options.cancelText.trim()
|
|
3562
|
+
: '取消';
|
|
3563
|
+
this.confirmDialogDanger = !!options.danger;
|
|
3564
|
+
this.showConfirmDialog = true;
|
|
3565
|
+
return new Promise((resolve) => {
|
|
3566
|
+
this.confirmDialogResolver = resolve;
|
|
3567
|
+
});
|
|
3568
|
+
},
|
|
3569
|
+
resolveConfirmDialog(confirmed) {
|
|
3570
|
+
const resolver = typeof this.confirmDialogResolver === 'function'
|
|
3571
|
+
? this.confirmDialogResolver
|
|
3572
|
+
: null;
|
|
3573
|
+
this.showConfirmDialog = false;
|
|
3574
|
+
this.confirmDialogTitle = '';
|
|
3575
|
+
this.confirmDialogMessage = '';
|
|
3576
|
+
this.confirmDialogConfirmText = '确认';
|
|
3577
|
+
this.confirmDialogCancelText = '取消';
|
|
3578
|
+
this.confirmDialogDanger = false;
|
|
3579
|
+
this.confirmDialogResolver = null;
|
|
3580
|
+
if (resolver) {
|
|
3581
|
+
resolver(!!confirmed);
|
|
3582
|
+
}
|
|
3583
|
+
},
|
|
3584
|
+
closeConfirmDialog() {
|
|
3585
|
+
this.resolveConfirmDialog(false);
|
|
3586
|
+
},
|
|
3587
|
+
onAgentsContentInput() {
|
|
3588
|
+
if (this.agentsDiffVisible || this.agentsDiffLines.length) {
|
|
3589
|
+
this.resetAgentsDiffState();
|
|
3590
|
+
}
|
|
3591
|
+
},
|
|
3592
|
+
buildAgentsDiffFingerprint() {
|
|
3593
|
+
const context = this.agentsContext || 'codex';
|
|
3594
|
+
const fileName = context === 'openclaw-workspace'
|
|
3595
|
+
? (this.agentsWorkspaceFileName || '')
|
|
3596
|
+
: '';
|
|
3597
|
+
const lineEnding = this.agentsLineEnding || '\n';
|
|
3598
|
+
const content = typeof this.agentsContent === 'string' ? this.agentsContent : '';
|
|
3599
|
+
const original = typeof this.agentsOriginalContent === 'string' ? this.agentsOriginalContent : '';
|
|
3600
|
+
return `${context}::${fileName}::${lineEnding}::${content.length}::${content}::${original.length}::${original}`;
|
|
3601
|
+
},
|
|
3602
|
+
async prepareAgentsDiff() {
|
|
3603
|
+
const requestFingerprint = this.buildAgentsDiffFingerprint();
|
|
3604
|
+
const requestToken = Symbol('agents-diff-preview');
|
|
3605
|
+
this._agentsDiffPreviewRequestToken = requestToken;
|
|
3606
|
+
this.agentsDiffVisible = true;
|
|
3607
|
+
this.agentsDiffLoading = true;
|
|
3608
|
+
this.agentsDiffError = '';
|
|
3609
|
+
this.agentsDiffLines = [];
|
|
3610
|
+
this.agentsDiffStats = {
|
|
3611
|
+
added: 0,
|
|
3612
|
+
removed: 0,
|
|
3613
|
+
unchanged: 0
|
|
3614
|
+
};
|
|
3615
|
+
this.agentsDiffTruncated = false;
|
|
3616
|
+
this.agentsDiffHasChangesValue = false;
|
|
3617
|
+
try {
|
|
3618
|
+
const shouldApplyPreviewState = () => shouldApplyAgentsDiffPreviewResponse({
|
|
3619
|
+
isVisible: this.agentsDiffVisible,
|
|
3620
|
+
requestToken,
|
|
3621
|
+
activeRequestToken: this._agentsDiffPreviewRequestToken,
|
|
3622
|
+
requestFingerprint,
|
|
3623
|
+
currentFingerprint: this.buildAgentsDiffFingerprint()
|
|
3624
|
+
});
|
|
3625
|
+
const applyPreviewState = (diff) => {
|
|
3626
|
+
if (!shouldApplyPreviewState()) {
|
|
3627
|
+
return false;
|
|
3628
|
+
}
|
|
3629
|
+
const normalizedDiff = diff && typeof diff === 'object' ? diff : {};
|
|
3630
|
+
const rawLines = Array.isArray(normalizedDiff.lines) ? normalizedDiff.lines : [];
|
|
3631
|
+
this.agentsDiffLines = rawLines.filter(line => line && line.type);
|
|
3632
|
+
this.agentsDiffTruncated = !!normalizedDiff.truncated;
|
|
3633
|
+
this.agentsDiffHasChangesValue = !!normalizedDiff.hasChanges;
|
|
3634
|
+
if (normalizedDiff.stats && typeof normalizedDiff.stats === 'object') {
|
|
3635
|
+
this.agentsDiffStats = {
|
|
3636
|
+
added: Number(normalizedDiff.stats.added || 0),
|
|
3637
|
+
removed: Number(normalizedDiff.stats.removed || 0),
|
|
3638
|
+
unchanged: Number(normalizedDiff.stats.unchanged || 0)
|
|
3639
|
+
};
|
|
3640
|
+
} else {
|
|
3641
|
+
const stats = { added: 0, removed: 0, unchanged: 0 };
|
|
3642
|
+
for (const line of this.agentsDiffLines) {
|
|
3643
|
+
if (line && line.type === 'add') stats.added += 1;
|
|
3644
|
+
else if (line && line.type === 'del') stats.removed += 1;
|
|
3645
|
+
else stats.unchanged += 1;
|
|
3646
|
+
}
|
|
3647
|
+
this.agentsDiffStats = stats;
|
|
3648
|
+
}
|
|
3649
|
+
this.agentsDiffFingerprint = requestFingerprint;
|
|
3650
|
+
return true;
|
|
3651
|
+
};
|
|
3652
|
+
const previewRequest = buildAgentsDiffPreviewRequest({
|
|
3653
|
+
baseContent: this.agentsOriginalContent,
|
|
3654
|
+
content: this.agentsContent,
|
|
3655
|
+
lineEnding: this.agentsLineEnding,
|
|
3656
|
+
context: this.agentsContext,
|
|
3657
|
+
fileName: this.agentsWorkspaceFileName
|
|
3658
|
+
});
|
|
3659
|
+
if (previewRequest.exceedsBodyLimit) {
|
|
3660
|
+
applyPreviewState(buildAgentsDiffPreview({
|
|
3661
|
+
baseContent: this.agentsOriginalContent,
|
|
3662
|
+
content: this.agentsContent
|
|
3663
|
+
}));
|
|
3664
|
+
return;
|
|
3665
|
+
}
|
|
3666
|
+
const res = await apiWithMeta('preview-agents-diff', previewRequest.params);
|
|
3667
|
+
if (!shouldApplyPreviewState()) {
|
|
3668
|
+
return;
|
|
3669
|
+
}
|
|
3670
|
+
if (res.error) {
|
|
3671
|
+
if (isAgentsDiffPreviewPayloadTooLarge(res)) {
|
|
3672
|
+
applyPreviewState(buildAgentsDiffPreview({
|
|
3673
|
+
baseContent: this.agentsOriginalContent,
|
|
3674
|
+
content: this.agentsContent
|
|
3675
|
+
}));
|
|
3676
|
+
return;
|
|
3677
|
+
}
|
|
3678
|
+
this.agentsDiffError = res.error;
|
|
3679
|
+
return;
|
|
3680
|
+
}
|
|
3681
|
+
applyPreviewState(res.diff);
|
|
3682
|
+
} catch (e) {
|
|
3683
|
+
if (shouldApplyAgentsDiffPreviewResponse({
|
|
3684
|
+
isVisible: this.agentsDiffVisible,
|
|
3685
|
+
requestToken,
|
|
3686
|
+
activeRequestToken: this._agentsDiffPreviewRequestToken,
|
|
3687
|
+
requestFingerprint,
|
|
3688
|
+
currentFingerprint: this.buildAgentsDiffFingerprint()
|
|
3689
|
+
})) {
|
|
3690
|
+
this.agentsDiffError = '生成差异失败';
|
|
3691
|
+
}
|
|
3692
|
+
} finally {
|
|
3693
|
+
if (this._agentsDiffPreviewRequestToken === requestToken) {
|
|
3694
|
+
this.agentsDiffLoading = false;
|
|
3695
|
+
}
|
|
3696
|
+
}
|
|
3697
|
+
},
|
|
3698
|
+
|
|
3699
|
+
async closeAgentsModal(options = {}) {
|
|
3700
|
+
const force = !!(options && options.force);
|
|
3701
|
+
const shouldConfirmClose = !force
|
|
3702
|
+
&& this.hasPendingAgentsDraft();
|
|
3703
|
+
if (shouldConfirmClose) {
|
|
3704
|
+
const message = this.agentsDiffVisible
|
|
3705
|
+
? '当前处于差异预览模式,改动尚未保存。确认放弃改动并关闭吗?'
|
|
3706
|
+
: '存在未保存改动,确认放弃改动并关闭吗?(关闭页面或应用也会丢失改动)';
|
|
3707
|
+
const confirmed = await this.requestConfirmDialog({
|
|
3708
|
+
title: '放弃未保存改动',
|
|
3709
|
+
message,
|
|
3710
|
+
confirmText: '放弃并关闭',
|
|
3711
|
+
cancelText: '继续编辑',
|
|
3712
|
+
danger: true
|
|
3713
|
+
});
|
|
3714
|
+
if (!confirmed) {
|
|
3715
|
+
return;
|
|
3716
|
+
}
|
|
3717
|
+
}
|
|
2232
3718
|
this.showAgentsModal = false;
|
|
2233
3719
|
this.agentsContent = '';
|
|
3720
|
+
this.agentsOriginalContent = '';
|
|
2234
3721
|
this.agentsPath = '';
|
|
2235
3722
|
this.agentsExists = false;
|
|
2236
3723
|
this.agentsLineEnding = '\n';
|
|
2237
3724
|
this.agentsSaving = false;
|
|
2238
3725
|
this.agentsWorkspaceFileName = '';
|
|
3726
|
+
this.resetAgentsDiffState();
|
|
2239
3727
|
this.setAgentsModalContext('codex');
|
|
2240
3728
|
},
|
|
2241
3729
|
|
|
2242
3730
|
async applyAgentsContent() {
|
|
3731
|
+
if (!this.agentsDiffVisible) {
|
|
3732
|
+
if (!this.hasAgentsContentChanged()) {
|
|
3733
|
+
this.showMessage('未检测到改动', 'info');
|
|
3734
|
+
return;
|
|
3735
|
+
}
|
|
3736
|
+
await this.prepareAgentsDiff();
|
|
3737
|
+
return;
|
|
3738
|
+
}
|
|
3739
|
+
if (this.agentsDiffLoading) {
|
|
3740
|
+
return;
|
|
3741
|
+
}
|
|
3742
|
+
if (this.agentsDiffError) {
|
|
3743
|
+
this.showMessage(this.agentsDiffError, 'error');
|
|
3744
|
+
return;
|
|
3745
|
+
}
|
|
3746
|
+
const fingerprint = this.buildAgentsDiffFingerprint();
|
|
3747
|
+
if (this.agentsDiffFingerprint !== fingerprint) {
|
|
3748
|
+
await this.prepareAgentsDiff();
|
|
3749
|
+
return;
|
|
3750
|
+
}
|
|
3751
|
+
if (!this.agentsDiffHasChanges) {
|
|
3752
|
+
this.showMessage('未检测到改动', 'info');
|
|
3753
|
+
return;
|
|
3754
|
+
}
|
|
2243
3755
|
this.agentsSaving = true;
|
|
2244
3756
|
try {
|
|
2245
3757
|
let action = 'apply-agents-file';
|
|
@@ -2262,7 +3774,7 @@ import { createSkillsMethods } from './modules/skills.methods.mjs';
|
|
|
2262
3774
|
? `工作区文件已保存${this.agentsWorkspaceFileName ? `: ${this.agentsWorkspaceFileName}` : ''}`
|
|
2263
3775
|
: (this.agentsContext === 'openclaw' ? 'OpenClaw AGENTS.md 已保存' : 'AGENTS.md 已保存');
|
|
2264
3776
|
this.showMessage(successLabel, 'success');
|
|
2265
|
-
this.closeAgentsModal();
|
|
3777
|
+
this.closeAgentsModal({ force: true });
|
|
2266
3778
|
} catch (e) {
|
|
2267
3779
|
this.showMessage('保存失败', 'error');
|
|
2268
3780
|
} finally {
|
|
@@ -2622,12 +4134,18 @@ import { createSkillsMethods } from './modules/skills.methods.mjs';
|
|
|
2622
4134
|
this.refreshClaudeModelContext();
|
|
2623
4135
|
},
|
|
2624
4136
|
|
|
2625
|
-
deleteClaudeConfig(name) {
|
|
4137
|
+
async deleteClaudeConfig(name) {
|
|
2626
4138
|
if (Object.keys(this.claudeConfigs).length <= 1) {
|
|
2627
4139
|
return this.showMessage('至少保留一项', 'error');
|
|
2628
4140
|
}
|
|
2629
|
-
|
|
2630
|
-
|
|
4141
|
+
const confirmed = await this.requestConfirmDialog({
|
|
4142
|
+
title: '删除 Claude 配置',
|
|
4143
|
+
message: `确定删除配置 "${name}"?`,
|
|
4144
|
+
confirmText: '删除',
|
|
4145
|
+
cancelText: '取消',
|
|
4146
|
+
danger: true
|
|
4147
|
+
});
|
|
4148
|
+
if (!confirmed) return;
|
|
2631
4149
|
|
|
2632
4150
|
delete this.claudeConfigs[name];
|
|
2633
4151
|
if (this.currentClaudeConfig === name) {
|
|
@@ -3713,11 +5231,18 @@ import { createSkillsMethods } from './modules/skills.methods.mjs';
|
|
|
3713
5231
|
}
|
|
3714
5232
|
},
|
|
3715
5233
|
|
|
3716
|
-
deleteOpenclawConfig(name) {
|
|
5234
|
+
async deleteOpenclawConfig(name) {
|
|
3717
5235
|
if (Object.keys(this.openclawConfigs).length <= 1) {
|
|
3718
5236
|
return this.showMessage('至少保留一项', 'error');
|
|
3719
5237
|
}
|
|
3720
|
-
|
|
5238
|
+
const confirmed = await this.requestConfirmDialog({
|
|
5239
|
+
title: '删除 OpenClaw 配置',
|
|
5240
|
+
message: `确定删除配置 "${name}"?`,
|
|
5241
|
+
confirmText: '删除',
|
|
5242
|
+
cancelText: '取消',
|
|
5243
|
+
danger: true
|
|
5244
|
+
});
|
|
5245
|
+
if (!confirmed) return;
|
|
3721
5246
|
delete this.openclawConfigs[name];
|
|
3722
5247
|
if (this.currentOpenclawConfig === name) {
|
|
3723
5248
|
this.currentOpenclawConfig = Object.keys(this.openclawConfigs)[0];
|
|
@@ -4243,4 +5768,5 @@ import { createSkillsMethods } from './modules/skills.methods.mjs';
|
|
|
4243
5768
|
|
|
4244
5769
|
app.mount('#app');
|
|
4245
5770
|
});
|
|
4246
|
-
|
|
5771
|
+
|
|
5772
|
+
|