codexmate 0.0.17 → 0.0.19
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 +65 -33
- package/README.md +61 -37
- package/cli.js +1877 -408
- package/lib/text-diff.js +303 -0
- package/package.json +1 -1
- package/web-ui/app.js +1749 -447
- package/web-ui/index.html +580 -199
- package/web-ui/logic.mjs +390 -0
- package/web-ui/modules/config-mode.computed.mjs +1 -0
- package/web-ui/modules/skills.computed.mjs +26 -1
- package/web-ui/modules/skills.methods.mjs +160 -23
- package/web-ui/session-helpers.mjs +362 -0
- package/web-ui/styles.css +652 -13
- package/doc/CHANGELOG.md +0 -32
- package/doc/CHANGELOG.zh-CN.md +0 -34
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,15 @@ 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
|
+
confirmDialogConfirmDisabled: false,
|
|
148
|
+
confirmDialogDisableWhen: null,
|
|
149
|
+
confirmDialogResolver: null,
|
|
100
150
|
configTemplateContent: '',
|
|
101
151
|
configTemplateApplying: false,
|
|
102
152
|
codexApplying: false,
|
|
@@ -106,9 +156,23 @@ import { createSkillsMethods } from './modules/skills.methods.mjs';
|
|
|
106
156
|
agentsLineEnding: '\n',
|
|
107
157
|
agentsLoading: false,
|
|
108
158
|
agentsSaving: false,
|
|
159
|
+
agentsOriginalContent: '',
|
|
160
|
+
agentsDiffVisible: false,
|
|
161
|
+
agentsDiffLoading: false,
|
|
162
|
+
agentsDiffError: '',
|
|
163
|
+
agentsDiffLines: [],
|
|
164
|
+
agentsDiffStats: {
|
|
165
|
+
added: 0,
|
|
166
|
+
removed: 0,
|
|
167
|
+
unchanged: 0
|
|
168
|
+
},
|
|
169
|
+
agentsDiffTruncated: false,
|
|
170
|
+
agentsDiffHasChangesValue: false,
|
|
171
|
+
agentsDiffFingerprint: '',
|
|
109
172
|
agentsContext: 'codex',
|
|
110
173
|
agentsModalTitle: 'AGENTS.md 编辑器',
|
|
111
174
|
agentsModalHint: '保存后会写入目标 AGENTS.md(与 config.toml 同级)。',
|
|
175
|
+
skillsTargetApp: 'codex',
|
|
112
176
|
skillsRootPath: '',
|
|
113
177
|
skillsList: [],
|
|
114
178
|
skillsSelectedNames: [],
|
|
@@ -122,7 +186,12 @@ import { createSkillsMethods } from './modules/skills.methods.mjs';
|
|
|
122
186
|
skillsImporting: false,
|
|
123
187
|
skillsZipImporting: false,
|
|
124
188
|
skillsExporting: false,
|
|
189
|
+
skillsMarketLoading: false,
|
|
190
|
+
skillsMarketLocalLoadedOnce: false,
|
|
191
|
+
skillsMarketImportLoadedOnce: false,
|
|
192
|
+
sessionPinnedMap: {},
|
|
125
193
|
sessionsList: [],
|
|
194
|
+
sessionsLoadedOnce: false,
|
|
126
195
|
sessionsLoading: false,
|
|
127
196
|
sessionFilterSource: 'all',
|
|
128
197
|
sessionPathFilter: '',
|
|
@@ -152,13 +221,31 @@ import { createSkillsMethods } from './modules/skills.methods.mjs';
|
|
|
152
221
|
activeSessionDetailClipped: false,
|
|
153
222
|
sessionDetailLoading: false,
|
|
154
223
|
sessionDetailRequestSeq: 0,
|
|
224
|
+
sessionDetailInitialMessageLimit: 80,
|
|
225
|
+
sessionDetailFetchStep: 80,
|
|
226
|
+
sessionDetailMessageLimit: 80,
|
|
227
|
+
sessionDetailMessageLimitCap: 1000,
|
|
155
228
|
sessionTimelineActiveKey: '',
|
|
156
229
|
sessionTimelineRafId: 0,
|
|
230
|
+
sessionTimelineLastSyncAt: 0,
|
|
231
|
+
sessionTimelineLastScrollTop: 0,
|
|
232
|
+
sessionTimelineLastAnchorY: 0,
|
|
233
|
+
sessionTimelineLastDirection: 0,
|
|
234
|
+
sessionTimelineEnabled: true,
|
|
157
235
|
sessionMessageRefMap: Object.create(null),
|
|
236
|
+
sessionMessageRefBinderMap: Object.create(null),
|
|
158
237
|
sessionPreviewScrollEl: null,
|
|
159
238
|
sessionPreviewContainerEl: null,
|
|
160
239
|
sessionPreviewHeaderEl: null,
|
|
161
240
|
sessionPreviewHeaderResizeObserver: null,
|
|
241
|
+
sessionListRenderEnabled: false,
|
|
242
|
+
sessionPreviewRenderEnabled: false,
|
|
243
|
+
sessionTabRenderTicket: 0,
|
|
244
|
+
sessionPreviewVisibleCount: 0,
|
|
245
|
+
sessionPreviewInitialBatchSize: 12,
|
|
246
|
+
sessionPreviewLoadStep: 24,
|
|
247
|
+
sessionPreviewPendingVisibleCount: 0,
|
|
248
|
+
sessionPreviewLoadingMore: false,
|
|
162
249
|
sessionStandalone: false,
|
|
163
250
|
sessionStandaloneError: '',
|
|
164
251
|
sessionStandaloneText: '',
|
|
@@ -275,27 +362,25 @@ import { createSkillsMethods } from './modules/skills.methods.mjs';
|
|
|
275
362
|
codexDownloadLoading: false,
|
|
276
363
|
codexDownloadProgress: 0,
|
|
277
364
|
codexDownloadTimer: null,
|
|
365
|
+
settingsTab: 'backup',
|
|
366
|
+
sessionTrashItems: [],
|
|
367
|
+
sessionTrashVisibleCount: SESSION_TRASH_PAGE_SIZE,
|
|
368
|
+
sessionTrashTotalCount: 0,
|
|
369
|
+
sessionTrashCountLoadedOnce: false,
|
|
370
|
+
sessionTrashLoadedOnce: false,
|
|
371
|
+
sessionTrashLastLoadFailed: false,
|
|
372
|
+
sessionTrashCountRequestToken: 0,
|
|
373
|
+
sessionTrashListRequestToken: 0,
|
|
374
|
+
sessionTrashCountPendingOptions: null,
|
|
375
|
+
sessionTrashPendingOptions: null,
|
|
376
|
+
sessionTrashCountLoading: false,
|
|
377
|
+
sessionTrashLoading: false,
|
|
378
|
+
sessionTrashRestoring: {},
|
|
379
|
+
sessionTrashPurging: {},
|
|
380
|
+
sessionTrashClearing: false,
|
|
278
381
|
claudeImportLoading: false,
|
|
279
382
|
codexImportLoading: false,
|
|
280
383
|
codexAuthProfiles: [],
|
|
281
|
-
codexAuthImportLoading: false,
|
|
282
|
-
codexAuthSwitching: {},
|
|
283
|
-
codexAuthDeleting: {},
|
|
284
|
-
proxySettings: {
|
|
285
|
-
enabled: false,
|
|
286
|
-
host: '127.0.0.1',
|
|
287
|
-
port: 8318,
|
|
288
|
-
provider: '',
|
|
289
|
-
authSource: 'provider',
|
|
290
|
-
timeoutMs: 30000
|
|
291
|
-
},
|
|
292
|
-
proxyRuntime: null,
|
|
293
|
-
proxyLoading: false,
|
|
294
|
-
proxySaving: false,
|
|
295
|
-
proxyStarting: false,
|
|
296
|
-
proxyStopping: false,
|
|
297
|
-
proxyApplying: false,
|
|
298
|
-
showProxyAdvanced: false,
|
|
299
384
|
forceCompactLayout: false
|
|
300
385
|
}
|
|
301
386
|
},
|
|
@@ -309,7 +394,10 @@ import { createSkillsMethods } from './modules/skills.methods.mjs';
|
|
|
309
394
|
this.sessionResumeWithYolo = true;
|
|
310
395
|
}
|
|
311
396
|
this.restoreSessionFilterCache();
|
|
397
|
+
this.restoreSessionPinnedMap();
|
|
312
398
|
window.addEventListener('resize', this.onWindowResize);
|
|
399
|
+
window.addEventListener('keydown', this.handleGlobalKeydown);
|
|
400
|
+
window.addEventListener('beforeunload', this.handleBeforeUnload);
|
|
313
401
|
const savedConfigs = localStorage.getItem('claudeConfigs');
|
|
314
402
|
if (savedConfigs) {
|
|
315
403
|
try {
|
|
@@ -346,25 +434,104 @@ import { createSkillsMethods } from './modules/skills.methods.mjs';
|
|
|
346
434
|
this.loadAll();
|
|
347
435
|
},
|
|
348
436
|
beforeUnmount() {
|
|
349
|
-
this.
|
|
437
|
+
this.teardownSessionTabRender();
|
|
438
|
+
this.cancelScheduledSessionTabDeferredTeardown();
|
|
350
439
|
this.disconnectSessionPreviewHeaderResizeObserver();
|
|
351
440
|
window.removeEventListener('resize', this.onWindowResize);
|
|
441
|
+
window.removeEventListener('keydown', this.handleGlobalKeydown);
|
|
442
|
+
window.removeEventListener('beforeunload', this.handleBeforeUnload);
|
|
352
443
|
this.applyCompactLayoutClass(false);
|
|
353
444
|
this.sessionPreviewScrollEl = null;
|
|
354
445
|
this.sessionPreviewContainerEl = null;
|
|
355
446
|
this.sessionPreviewHeaderEl = null;
|
|
356
|
-
this.
|
|
447
|
+
this.clearSessionTimelineRefs();
|
|
357
448
|
},
|
|
358
449
|
|
|
359
450
|
computed: {
|
|
360
451
|
isSessionQueryEnabled() {
|
|
361
452
|
return isSessionQueryEnabled(this.sessionFilterSource);
|
|
362
453
|
},
|
|
454
|
+
activeSessionExportKey() {
|
|
455
|
+
return this.activeSession ? this.getSessionExportKey(this.activeSession) : '';
|
|
456
|
+
},
|
|
457
|
+
sortedSessionsList() {
|
|
458
|
+
const list = Array.isArray(this.sessionsList) ? this.sessionsList : [];
|
|
459
|
+
if (list.length === 0) return [];
|
|
460
|
+
const pinnedMap = (this.sessionPinnedMap && typeof this.sessionPinnedMap === 'object')
|
|
461
|
+
? this.sessionPinnedMap
|
|
462
|
+
: {};
|
|
463
|
+
let hasPinned = false;
|
|
464
|
+
const decorated = list.map((session, index) => {
|
|
465
|
+
const key = session ? this.getSessionExportKey(session) : '';
|
|
466
|
+
const rawPinnedAt = key ? pinnedMap[key] : 0;
|
|
467
|
+
const pinnedAt = Number.isFinite(Number(rawPinnedAt))
|
|
468
|
+
? Math.floor(Number(rawPinnedAt))
|
|
469
|
+
: 0;
|
|
470
|
+
const isPinned = pinnedAt > 0;
|
|
471
|
+
if (isPinned) {
|
|
472
|
+
hasPinned = true;
|
|
473
|
+
}
|
|
474
|
+
return { session, index, pinnedAt, isPinned };
|
|
475
|
+
});
|
|
476
|
+
if (!hasPinned) return list;
|
|
477
|
+
decorated.sort((a, b) => {
|
|
478
|
+
if (a.isPinned !== b.isPinned) return a.isPinned ? -1 : 1;
|
|
479
|
+
if (a.isPinned && a.pinnedAt !== b.pinnedAt) return b.pinnedAt - a.pinnedAt;
|
|
480
|
+
return a.index - b.index;
|
|
481
|
+
});
|
|
482
|
+
return decorated.map(item => item.session);
|
|
483
|
+
},
|
|
484
|
+
activeSessionVisibleMessages() {
|
|
485
|
+
if (this.mainTab !== 'sessions' || !this.sessionPreviewRenderEnabled) {
|
|
486
|
+
return [];
|
|
487
|
+
}
|
|
488
|
+
const list = Array.isArray(this.activeSessionMessages) ? this.activeSessionMessages : [];
|
|
489
|
+
const rawCount = Number(this.sessionPreviewVisibleCount);
|
|
490
|
+
const visibleCount = Number.isFinite(rawCount)
|
|
491
|
+
? Math.max(0, Math.floor(rawCount))
|
|
492
|
+
: 0;
|
|
493
|
+
if (visibleCount <= 0) {
|
|
494
|
+
if (!list.length) return [];
|
|
495
|
+
// Defensive fallback: avoid getting stuck in "正在渲染会话内容..."
|
|
496
|
+
// when visible count has not been primed yet.
|
|
497
|
+
return list.slice(0, Math.min(8, list.length));
|
|
498
|
+
}
|
|
499
|
+
if (visibleCount >= list.length) return list;
|
|
500
|
+
return list.slice(0, visibleCount);
|
|
501
|
+
},
|
|
502
|
+
canLoadMoreSessionMessages() {
|
|
503
|
+
if (this.mainTab !== 'sessions' || !this.sessionPreviewRenderEnabled) {
|
|
504
|
+
return false;
|
|
505
|
+
}
|
|
506
|
+
const total = Array.isArray(this.activeSessionMessages) ? this.activeSessionMessages.length : 0;
|
|
507
|
+
const visible = Array.isArray(this.activeSessionVisibleMessages) ? this.activeSessionVisibleMessages.length : 0;
|
|
508
|
+
return total > visible;
|
|
509
|
+
},
|
|
510
|
+
sessionPreviewRemainingCount() {
|
|
511
|
+
const total = Array.isArray(this.activeSessionMessages) ? this.activeSessionMessages.length : 0;
|
|
512
|
+
const visible = Array.isArray(this.activeSessionVisibleMessages) ? this.activeSessionVisibleMessages.length : 0;
|
|
513
|
+
return Math.max(0, total - visible);
|
|
514
|
+
},
|
|
363
515
|
sessionTimelineNodes() {
|
|
364
|
-
|
|
516
|
+
if (this.mainTab !== 'sessions' || !this.sessionPreviewRenderEnabled) {
|
|
517
|
+
return [];
|
|
518
|
+
}
|
|
519
|
+
return buildSessionTimelineNodes(this.activeSessionVisibleMessages, {
|
|
365
520
|
getKey: (message, index) => this.getRecordRenderKey(message, index)
|
|
366
521
|
});
|
|
367
522
|
},
|
|
523
|
+
sessionTimelineNodeKeyMap() {
|
|
524
|
+
const nodes = Array.isArray(this.sessionTimelineNodes) ? this.sessionTimelineNodes : [];
|
|
525
|
+
if (!nodes.length) {
|
|
526
|
+
return Object.create(null);
|
|
527
|
+
}
|
|
528
|
+
const map = Object.create(null);
|
|
529
|
+
for (const node of nodes) {
|
|
530
|
+
if (!node || !node.key) continue;
|
|
531
|
+
map[node.key] = true;
|
|
532
|
+
}
|
|
533
|
+
return map;
|
|
534
|
+
},
|
|
368
535
|
sessionTimelineActiveTitle() {
|
|
369
536
|
if (!this.sessionTimelineActiveKey) return '';
|
|
370
537
|
const nodes = Array.isArray(this.sessionTimelineNodes) ? this.sessionTimelineNodes : [];
|
|
@@ -377,6 +544,15 @@ import { createSkillsMethods } from './modules/skills.methods.mjs';
|
|
|
377
544
|
}
|
|
378
545
|
return '当前来源暂不支持关键词检索';
|
|
379
546
|
},
|
|
547
|
+
agentsDiffHasChanges() {
|
|
548
|
+
if (this.agentsDiffTruncated) {
|
|
549
|
+
return !!this.agentsDiffHasChangesValue;
|
|
550
|
+
}
|
|
551
|
+
const stats = this.agentsDiffStats || {};
|
|
552
|
+
const added = Number(stats.added || 0);
|
|
553
|
+
const removed = Number(stats.removed || 0);
|
|
554
|
+
return added > 0 || removed > 0;
|
|
555
|
+
},
|
|
380
556
|
claudeModelHasList() {
|
|
381
557
|
return Array.isArray(this.claudeModels) && this.claudeModels.length > 0;
|
|
382
558
|
},
|
|
@@ -388,19 +564,16 @@ import { createSkillsMethods } from './modules/skills.methods.mjs';
|
|
|
388
564
|
}
|
|
389
565
|
return list;
|
|
390
566
|
},
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
return
|
|
397
|
-
},
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
? this.proxyRuntime.provider.trim()
|
|
402
|
-
: '';
|
|
403
|
-
return value || 'local';
|
|
567
|
+
hasLocalAndProxy() {
|
|
568
|
+
return false;
|
|
569
|
+
},
|
|
570
|
+
displayCurrentProvider() {
|
|
571
|
+
const current = String(this.currentProvider || '').trim();
|
|
572
|
+
return current;
|
|
573
|
+
},
|
|
574
|
+
displayProvidersList() {
|
|
575
|
+
const list = Array.isArray(this.providersList) ? this.providersList : [];
|
|
576
|
+
return list.filter((item) => String(item && item.name ? item.name : '').trim().toLowerCase() !== 'codexmate-proxy');
|
|
404
577
|
},
|
|
405
578
|
installTargetCards() {
|
|
406
579
|
const targets = Array.isArray(this.installStatusTargets) ? this.installStatusTargets : [];
|
|
@@ -416,6 +589,29 @@ import { createSkillsMethods } from './modules/skills.methods.mjs';
|
|
|
416
589
|
installRegistryPreview() {
|
|
417
590
|
return this.resolveInstallRegistryUrl(this.installRegistryPreset, this.installRegistryCustom);
|
|
418
591
|
},
|
|
592
|
+
visibleSessionTrashItems() {
|
|
593
|
+
const items = Array.isArray(this.sessionTrashItems) ? this.sessionTrashItems : [];
|
|
594
|
+
const visibleCount = Number(this.sessionTrashVisibleCount);
|
|
595
|
+
const safeVisibleCount = Number.isFinite(visibleCount) && visibleCount > 0
|
|
596
|
+
? Math.floor(visibleCount)
|
|
597
|
+
: SESSION_TRASH_PAGE_SIZE;
|
|
598
|
+
return items.slice(0, safeVisibleCount);
|
|
599
|
+
},
|
|
600
|
+
sessionTrashHasMoreItems() {
|
|
601
|
+
const totalItems = Array.isArray(this.sessionTrashItems) ? this.sessionTrashItems.length : 0;
|
|
602
|
+
return this.visibleSessionTrashItems.length < totalItems;
|
|
603
|
+
},
|
|
604
|
+
sessionTrashHiddenCount() {
|
|
605
|
+
const totalItems = Array.isArray(this.sessionTrashItems) ? this.sessionTrashItems.length : 0;
|
|
606
|
+
return Math.max(0, totalItems - this.visibleSessionTrashItems.length);
|
|
607
|
+
},
|
|
608
|
+
sessionTrashCount() {
|
|
609
|
+
const totalCount = Number(this.sessionTrashTotalCount);
|
|
610
|
+
if (Number.isFinite(totalCount) && totalCount >= 0) {
|
|
611
|
+
return Math.max(0, Math.floor(totalCount));
|
|
612
|
+
}
|
|
613
|
+
return Array.isArray(this.sessionTrashItems) ? this.sessionTrashItems.length : 0;
|
|
614
|
+
},
|
|
419
615
|
...createSkillsComputed(),
|
|
420
616
|
|
|
421
617
|
...createConfigModeComputed(),
|
|
@@ -428,7 +624,6 @@ import { createSkillsMethods } from './modules/skills.methods.mjs';
|
|
|
428
624
|
if (this.codexApplying || this.configTemplateApplying || this.openclawApplying) tasks.push('配置应用');
|
|
429
625
|
if (this.agentsSaving) tasks.push('AGENTS 保存');
|
|
430
626
|
if (this.skillsLoading || this.skillsDeleting || this.skillsScanningImports || this.skillsImporting || this.skillsZipImporting || this.skillsExporting) tasks.push('Skills 管理');
|
|
431
|
-
if (this.proxySaving || this.proxyApplying || this.proxyStarting || this.proxyStopping) tasks.push('代理更新');
|
|
432
627
|
return tasks.length ? tasks.join(' / ') : '空闲';
|
|
433
628
|
},
|
|
434
629
|
inspectorMessageSummary() {
|
|
@@ -468,15 +663,6 @@ import { createSkillsMethods } from './modules/skills.methods.mjs';
|
|
|
468
663
|
}
|
|
469
664
|
return '正常';
|
|
470
665
|
},
|
|
471
|
-
inspectorProxyStatus() {
|
|
472
|
-
if (this.proxySaving || this.proxyApplying || this.proxyStarting || this.proxyStopping) {
|
|
473
|
-
return '状态更新中';
|
|
474
|
-
}
|
|
475
|
-
if (this.proxyRuntime && this.proxyRuntime.running === true) {
|
|
476
|
-
return `运行中(${this.proxyRuntimeDisplayProvider})`;
|
|
477
|
-
}
|
|
478
|
-
return '未运行';
|
|
479
|
-
},
|
|
480
666
|
installTroubleshootingTips() {
|
|
481
667
|
const platform = this.resolveInstallPlatform();
|
|
482
668
|
if (platform === 'win32') {
|
|
@@ -540,12 +726,9 @@ import { createSkillsMethods } from './modules/skills.methods.mjs';
|
|
|
540
726
|
}
|
|
541
727
|
|
|
542
728
|
try {
|
|
543
|
-
await
|
|
544
|
-
this.loadCodexAuthProfiles(),
|
|
545
|
-
this.loadProxyStatus()
|
|
546
|
-
]);
|
|
729
|
+
await this.loadCodexAuthProfiles();
|
|
547
730
|
} catch (e) {
|
|
548
|
-
//
|
|
731
|
+
// 认证状态加载失败不阻塞主界面
|
|
549
732
|
}
|
|
550
733
|
},
|
|
551
734
|
|
|
@@ -807,21 +990,413 @@ import { createSkillsMethods } from './modules/skills.methods.mjs';
|
|
|
807
990
|
const normalizedMode = typeof mode === 'string'
|
|
808
991
|
? mode.trim().toLowerCase()
|
|
809
992
|
: '';
|
|
810
|
-
this.mainTab = 'config';
|
|
811
993
|
this.configMode = CONFIG_MODE_SET.has(normalizedMode) ? normalizedMode : 'codex';
|
|
812
|
-
if (this.
|
|
813
|
-
this.
|
|
994
|
+
if (this.mainTab === 'config') {
|
|
995
|
+
if (this.configMode === 'claude') {
|
|
996
|
+
const expectedMainTab = 'config';
|
|
997
|
+
const expectedConfigMode = 'claude';
|
|
998
|
+
const refresh = () => {
|
|
999
|
+
if (this.mainTab !== expectedMainTab || this.configMode !== expectedConfigMode) {
|
|
1000
|
+
return;
|
|
1001
|
+
}
|
|
1002
|
+
this.refreshClaudeModelContext();
|
|
1003
|
+
};
|
|
1004
|
+
if (typeof this.scheduleAfterFrame === 'function') {
|
|
1005
|
+
this.scheduleAfterFrame(refresh);
|
|
1006
|
+
} else {
|
|
1007
|
+
refresh();
|
|
1008
|
+
}
|
|
1009
|
+
}
|
|
1010
|
+
this.scheduleAfterFrame(() => {
|
|
1011
|
+
this.clearMainTabSwitchIntent('config');
|
|
1012
|
+
});
|
|
1013
|
+
return;
|
|
814
1014
|
}
|
|
1015
|
+
this.switchMainTab('config');
|
|
815
1016
|
},
|
|
816
1017
|
|
|
1018
|
+
ensureMainTabSwitchState() {
|
|
1019
|
+
if (this.__mainTabSwitchState) {
|
|
1020
|
+
return this.__mainTabSwitchState;
|
|
1021
|
+
}
|
|
1022
|
+
this.__mainTabSwitchState = {
|
|
1023
|
+
intent: '',
|
|
1024
|
+
pendingTarget: '',
|
|
1025
|
+
ticket: 0
|
|
1026
|
+
};
|
|
1027
|
+
return this.__mainTabSwitchState;
|
|
1028
|
+
},
|
|
1029
|
+
ensureImmediateNavDomState() {
|
|
1030
|
+
if (typeof document === 'undefined') {
|
|
1031
|
+
return {
|
|
1032
|
+
navNodes: [],
|
|
1033
|
+
sessionPanelEl: null
|
|
1034
|
+
};
|
|
1035
|
+
}
|
|
1036
|
+
if (!this.__immediateNavDomState) {
|
|
1037
|
+
this.__immediateNavDomState = {
|
|
1038
|
+
navNodes: [],
|
|
1039
|
+
sessionPanelEl: null
|
|
1040
|
+
};
|
|
1041
|
+
}
|
|
1042
|
+
const state = this.__immediateNavDomState;
|
|
1043
|
+
const needsNavRefresh = !Array.isArray(state.navNodes)
|
|
1044
|
+
|| !state.navNodes.length
|
|
1045
|
+
|| state.navNodes.some((node) => !node || !node.isConnected);
|
|
1046
|
+
if (needsNavRefresh) {
|
|
1047
|
+
state.navNodes = Array.from(document.querySelectorAll('[data-main-tab]'));
|
|
1048
|
+
}
|
|
1049
|
+
if (!state.sessionPanelEl || !state.sessionPanelEl.isConnected) {
|
|
1050
|
+
state.sessionPanelEl = document.getElementById('panel-sessions');
|
|
1051
|
+
}
|
|
1052
|
+
return state;
|
|
1053
|
+
},
|
|
1054
|
+
setMainTabSwitchIntent(tab) {
|
|
1055
|
+
const normalizedTab = typeof tab === 'string'
|
|
1056
|
+
? tab.trim().toLowerCase()
|
|
1057
|
+
: '';
|
|
1058
|
+
if (!normalizedTab) return;
|
|
1059
|
+
const state = this.ensureMainTabSwitchState();
|
|
1060
|
+
state.intent = normalizedTab;
|
|
1061
|
+
},
|
|
1062
|
+
applyImmediateNavIntent(tab, configMode = '') {
|
|
1063
|
+
if (typeof document === 'undefined') return;
|
|
1064
|
+
const normalizedTab = typeof tab === 'string' ? tab.trim().toLowerCase() : '';
|
|
1065
|
+
if (!normalizedTab) return;
|
|
1066
|
+
const normalizedMode = typeof configMode === 'string' ? configMode.trim().toLowerCase() : '';
|
|
1067
|
+
const domState = this.ensureImmediateNavDomState();
|
|
1068
|
+
const nodes = Array.isArray(domState.navNodes) ? domState.navNodes : [];
|
|
1069
|
+
for (const node of nodes) {
|
|
1070
|
+
if (!node || !node.classList) continue;
|
|
1071
|
+
const nodeTab = String(node.getAttribute('data-main-tab') || '').trim().toLowerCase();
|
|
1072
|
+
const nodeMode = String(node.getAttribute('data-config-mode') || '').trim().toLowerCase();
|
|
1073
|
+
let shouldActivate = nodeTab === normalizedTab;
|
|
1074
|
+
if (shouldActivate && normalizedTab === 'config') {
|
|
1075
|
+
shouldActivate = nodeMode ? nodeMode === normalizedMode : false;
|
|
1076
|
+
}
|
|
1077
|
+
node.classList.toggle('nav-intent-active', !!shouldActivate);
|
|
1078
|
+
node.classList.toggle('nav-intent-inactive', !shouldActivate);
|
|
1079
|
+
}
|
|
1080
|
+
},
|
|
1081
|
+
clearImmediateNavIntent() {
|
|
1082
|
+
if (typeof document === 'undefined') return;
|
|
1083
|
+
const domState = this.ensureImmediateNavDomState();
|
|
1084
|
+
const nodes = Array.isArray(domState.navNodes) ? domState.navNodes : [];
|
|
1085
|
+
for (const node of nodes) {
|
|
1086
|
+
if (!node || !node.classList) continue;
|
|
1087
|
+
node.classList.remove('nav-intent-active');
|
|
1088
|
+
node.classList.remove('nav-intent-inactive');
|
|
1089
|
+
}
|
|
1090
|
+
},
|
|
1091
|
+
setSessionPanelFastHidden(hidden) {
|
|
1092
|
+
if (typeof document === 'undefined') return;
|
|
1093
|
+
const domState = this.ensureImmediateNavDomState();
|
|
1094
|
+
const panel = domState.sessionPanelEl;
|
|
1095
|
+
if (!panel || !panel.classList) return;
|
|
1096
|
+
panel.classList.toggle('session-panel-fast-hidden', !!hidden);
|
|
1097
|
+
},
|
|
1098
|
+
isSessionPanelFastHidden() {
|
|
1099
|
+
if (typeof document === 'undefined') return false;
|
|
1100
|
+
const domState = this.ensureImmediateNavDomState();
|
|
1101
|
+
const panel = domState.sessionPanelEl;
|
|
1102
|
+
return !!(panel && panel.classList && panel.classList.contains('session-panel-fast-hidden'));
|
|
1103
|
+
},
|
|
1104
|
+
recordPointerNavCommit(kind, value) {
|
|
1105
|
+
const normalizedKind = typeof kind === 'string' ? kind.trim().toLowerCase() : '';
|
|
1106
|
+
const normalizedValue = typeof value === 'string' ? value.trim().toLowerCase() : '';
|
|
1107
|
+
if (!normalizedKind || !normalizedValue) {
|
|
1108
|
+
this.__pointerNavCommitState = null;
|
|
1109
|
+
return;
|
|
1110
|
+
}
|
|
1111
|
+
this.__pointerNavCommitState = {
|
|
1112
|
+
kind: normalizedKind,
|
|
1113
|
+
value: normalizedValue,
|
|
1114
|
+
at: Date.now()
|
|
1115
|
+
};
|
|
1116
|
+
},
|
|
1117
|
+
consumePointerNavCommit(kind, value) {
|
|
1118
|
+
const normalizedKind = typeof kind === 'string' ? kind.trim().toLowerCase() : '';
|
|
1119
|
+
const normalizedValue = typeof value === 'string' ? value.trim().toLowerCase() : '';
|
|
1120
|
+
const state = this.__pointerNavCommitState;
|
|
1121
|
+
this.__pointerNavCommitState = null;
|
|
1122
|
+
if (!state || !normalizedKind || !normalizedValue) {
|
|
1123
|
+
return false;
|
|
1124
|
+
}
|
|
1125
|
+
if (state.kind !== normalizedKind || state.value !== normalizedValue) {
|
|
1126
|
+
return false;
|
|
1127
|
+
}
|
|
1128
|
+
return (Date.now() - Number(state.at || 0)) <= 1000;
|
|
1129
|
+
},
|
|
1130
|
+
onMainTabPointerDown(tab) {
|
|
1131
|
+
const event = arguments.length > 1 ? arguments[1] : null;
|
|
1132
|
+
if (event && typeof event.button === 'number' && event.button !== 0) {
|
|
1133
|
+
return;
|
|
1134
|
+
}
|
|
1135
|
+
const normalizedTab = typeof tab === 'string' ? tab.trim().toLowerCase() : '';
|
|
1136
|
+
if (!normalizedTab) return;
|
|
1137
|
+
this.setMainTabSwitchIntent(normalizedTab);
|
|
1138
|
+
this.applyImmediateNavIntent(normalizedTab);
|
|
1139
|
+
const shouldHideSessionPanel = this.mainTab === 'sessions' && normalizedTab !== 'sessions';
|
|
1140
|
+
this.setSessionPanelFastHidden(shouldHideSessionPanel);
|
|
1141
|
+
const pointerType = event && typeof event.pointerType === 'string'
|
|
1142
|
+
? event.pointerType.trim().toLowerCase()
|
|
1143
|
+
: '';
|
|
1144
|
+
if (pointerType === 'touch') {
|
|
1145
|
+
return;
|
|
1146
|
+
}
|
|
1147
|
+
this.recordPointerNavCommit('main', normalizedTab);
|
|
1148
|
+
this.switchMainTab(normalizedTab);
|
|
1149
|
+
},
|
|
1150
|
+
onConfigTabPointerDown(mode) {
|
|
1151
|
+
const event = arguments.length > 1 ? arguments[1] : null;
|
|
1152
|
+
if (event && typeof event.button === 'number' && event.button !== 0) {
|
|
1153
|
+
return;
|
|
1154
|
+
}
|
|
1155
|
+
const normalizedMode = typeof mode === 'string' ? mode.trim().toLowerCase() : '';
|
|
1156
|
+
if (!normalizedMode) return;
|
|
1157
|
+
this.setMainTabSwitchIntent('config');
|
|
1158
|
+
this.applyImmediateNavIntent('config', normalizedMode);
|
|
1159
|
+
const shouldHideSessionPanel = this.mainTab === 'sessions';
|
|
1160
|
+
this.setSessionPanelFastHidden(shouldHideSessionPanel);
|
|
1161
|
+
const pointerType = event && typeof event.pointerType === 'string'
|
|
1162
|
+
? event.pointerType.trim().toLowerCase()
|
|
1163
|
+
: '';
|
|
1164
|
+
if (pointerType === 'touch') {
|
|
1165
|
+
return;
|
|
1166
|
+
}
|
|
1167
|
+
this.recordPointerNavCommit('config', normalizedMode);
|
|
1168
|
+
this.switchConfigMode(normalizedMode);
|
|
1169
|
+
},
|
|
1170
|
+
onMainTabClick(tab) {
|
|
1171
|
+
const normalizedTab = typeof tab === 'string' ? tab.trim().toLowerCase() : '';
|
|
1172
|
+
if (!normalizedTab) return;
|
|
1173
|
+
if (this.consumePointerNavCommit('main', normalizedTab)) return;
|
|
1174
|
+
this.switchMainTab(normalizedTab);
|
|
1175
|
+
},
|
|
1176
|
+
onConfigTabClick(mode) {
|
|
1177
|
+
const normalizedMode = typeof mode === 'string' ? mode.trim().toLowerCase() : '';
|
|
1178
|
+
if (!normalizedMode) return;
|
|
1179
|
+
if (this.consumePointerNavCommit('config', normalizedMode)) return;
|
|
1180
|
+
this.switchConfigMode(normalizedMode);
|
|
1181
|
+
},
|
|
1182
|
+
clearMainTabSwitchIntent(expectedTab = '') {
|
|
1183
|
+
const state = this.ensureMainTabSwitchState();
|
|
1184
|
+
if (expectedTab && state.intent && state.intent !== expectedTab) {
|
|
1185
|
+
return;
|
|
1186
|
+
}
|
|
1187
|
+
state.intent = '';
|
|
1188
|
+
state.pendingTarget = '';
|
|
1189
|
+
this.clearImmediateNavIntent();
|
|
1190
|
+
this.setSessionPanelFastHidden(false);
|
|
1191
|
+
},
|
|
1192
|
+
getMainTabForNav() {
|
|
1193
|
+
const state = this.ensureMainTabSwitchState();
|
|
1194
|
+
return state.intent || this.mainTab;
|
|
1195
|
+
},
|
|
1196
|
+
isMainTabNavActive(tab) {
|
|
1197
|
+
return this.getMainTabForNav() === tab;
|
|
1198
|
+
},
|
|
1199
|
+
isConfigModeNavActive(mode) {
|
|
1200
|
+
return this.isMainTabNavActive('config') && this.configMode === mode;
|
|
1201
|
+
},
|
|
817
1202
|
switchMainTab(tab) {
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
1203
|
+
const normalizedTab = typeof tab === 'string'
|
|
1204
|
+
? tab.trim().toLowerCase()
|
|
1205
|
+
: '';
|
|
1206
|
+
const targetTab = normalizedTab || tab;
|
|
1207
|
+
if (!targetTab) return;
|
|
1208
|
+
if (targetTab === 'sessions') {
|
|
1209
|
+
this.cancelScheduledSessionTabDeferredTeardown();
|
|
821
1210
|
}
|
|
822
|
-
|
|
823
|
-
|
|
1211
|
+
|
|
1212
|
+
this.setMainTabSwitchIntent(targetTab);
|
|
1213
|
+
if (targetTab === 'config') {
|
|
1214
|
+
this.applyImmediateNavIntent('config', this.configMode);
|
|
1215
|
+
} else {
|
|
1216
|
+
this.applyImmediateNavIntent(targetTab);
|
|
1217
|
+
}
|
|
1218
|
+
|
|
1219
|
+
const previousTab = this.mainTab;
|
|
1220
|
+
const switchState = this.ensureMainTabSwitchState();
|
|
1221
|
+
if (targetTab === previousTab) {
|
|
1222
|
+
switchState.ticket += 1;
|
|
1223
|
+
switchState.pendingTarget = '';
|
|
1224
|
+
this.scheduleAfterFrame(() => {
|
|
1225
|
+
this.clearMainTabSwitchIntent(normalizedTab);
|
|
1226
|
+
});
|
|
1227
|
+
return;
|
|
1228
|
+
}
|
|
1229
|
+
const isLeavingSessions = previousTab === 'sessions' && targetTab !== 'sessions';
|
|
1230
|
+
const shouldDeferApply = isLeavingSessions;
|
|
1231
|
+
if (isLeavingSessions && !this.isSessionPanelFastHidden()) {
|
|
1232
|
+
this.setSessionPanelFastHidden(true);
|
|
1233
|
+
}
|
|
1234
|
+
if (!shouldDeferApply) {
|
|
1235
|
+
switchState.ticket += 1;
|
|
1236
|
+
switchState.pendingTarget = '';
|
|
1237
|
+
const result = switchMainTabHelper.call(this, targetTab);
|
|
1238
|
+
this.scheduleAfterFrame(() => {
|
|
1239
|
+
this.clearMainTabSwitchIntent(normalizedTab);
|
|
1240
|
+
});
|
|
1241
|
+
return result;
|
|
1242
|
+
}
|
|
1243
|
+
|
|
1244
|
+
const ticket = ++switchState.ticket;
|
|
1245
|
+
switchState.pendingTarget = targetTab;
|
|
1246
|
+
this.scheduleAfterFrame(() => {
|
|
1247
|
+
const liveState = this.ensureMainTabSwitchState();
|
|
1248
|
+
if (ticket !== liveState.ticket) return;
|
|
1249
|
+
const pendingTarget = liveState.pendingTarget || targetTab;
|
|
1250
|
+
liveState.pendingTarget = '';
|
|
1251
|
+
switchMainTabHelper.call(this, pendingTarget);
|
|
1252
|
+
this.clearMainTabSwitchIntent(normalizedTab);
|
|
1253
|
+
});
|
|
1254
|
+
},
|
|
1255
|
+
|
|
1256
|
+
scheduleAfterFrame(task) {
|
|
1257
|
+
const callback = typeof task === 'function' ? task : () => {};
|
|
1258
|
+
if (typeof requestAnimationFrame === 'function') {
|
|
1259
|
+
requestAnimationFrame(callback);
|
|
1260
|
+
return;
|
|
1261
|
+
}
|
|
1262
|
+
setTimeout(callback, 16);
|
|
1263
|
+
},
|
|
1264
|
+
scheduleIdleTask(task, timeoutMs = 160) {
|
|
1265
|
+
const callback = typeof task === 'function' ? task : () => {};
|
|
1266
|
+
const timeout = Number.isFinite(timeoutMs)
|
|
1267
|
+
? Math.max(16, Math.floor(timeoutMs))
|
|
1268
|
+
: 160;
|
|
1269
|
+
if (typeof requestIdleCallback === 'function') {
|
|
1270
|
+
const id = requestIdleCallback(callback, { timeout });
|
|
1271
|
+
return {
|
|
1272
|
+
type: 'idle',
|
|
1273
|
+
id
|
|
1274
|
+
};
|
|
1275
|
+
}
|
|
1276
|
+
const id = setTimeout(callback, timeout);
|
|
1277
|
+
return {
|
|
1278
|
+
type: 'timeout',
|
|
1279
|
+
id
|
|
1280
|
+
};
|
|
1281
|
+
},
|
|
1282
|
+
cancelIdleTask(handle) {
|
|
1283
|
+
if (!handle || typeof handle !== 'object') return;
|
|
1284
|
+
const type = handle.type;
|
|
1285
|
+
const id = handle.id;
|
|
1286
|
+
if (type === 'idle') {
|
|
1287
|
+
if (typeof cancelIdleCallback === 'function') {
|
|
1288
|
+
cancelIdleCallback(id);
|
|
1289
|
+
} else {
|
|
1290
|
+
clearTimeout(id);
|
|
1291
|
+
}
|
|
1292
|
+
return;
|
|
1293
|
+
}
|
|
1294
|
+
if (type === 'timeout') {
|
|
1295
|
+
clearTimeout(id);
|
|
1296
|
+
}
|
|
1297
|
+
},
|
|
1298
|
+
scheduleSessionTabDeferredTeardown(task) {
|
|
1299
|
+
const callback = typeof task === 'function' ? task : () => {};
|
|
1300
|
+
this.cancelScheduledSessionTabDeferredTeardown();
|
|
1301
|
+
this.__sessionTabDeferredTeardownHandle = this.scheduleIdleTask(() => {
|
|
1302
|
+
this.__sessionTabDeferredTeardownHandle = null;
|
|
1303
|
+
callback();
|
|
1304
|
+
}, 180);
|
|
1305
|
+
},
|
|
1306
|
+
cancelScheduledSessionTabDeferredTeardown() {
|
|
1307
|
+
const handle = this.__sessionTabDeferredTeardownHandle || null;
|
|
1308
|
+
if (!handle) return;
|
|
1309
|
+
this.cancelIdleTask(handle);
|
|
1310
|
+
this.__sessionTabDeferredTeardownHandle = null;
|
|
1311
|
+
},
|
|
1312
|
+
|
|
1313
|
+
resetSessionPreviewMessageRender() {
|
|
1314
|
+
this.sessionPreviewVisibleCount = 0;
|
|
1315
|
+
this.invalidateSessionTimelineMeasurementCache();
|
|
1316
|
+
},
|
|
1317
|
+
|
|
1318
|
+
resetSessionDetailPagination() {
|
|
1319
|
+
const initialLimit = Number.isFinite(this.sessionDetailInitialMessageLimit)
|
|
1320
|
+
? Math.max(1, Math.floor(this.sessionDetailInitialMessageLimit))
|
|
1321
|
+
: 80;
|
|
1322
|
+
this.sessionDetailMessageLimit = initialLimit;
|
|
1323
|
+
this.sessionPreviewPendingVisibleCount = 0;
|
|
1324
|
+
},
|
|
1325
|
+
|
|
1326
|
+
primeSessionPreviewMessageRender() {
|
|
1327
|
+
this.sessionPreviewVisibleCount = 0;
|
|
1328
|
+
this.invalidateSessionTimelineMeasurementCache();
|
|
1329
|
+
if (this.mainTab !== 'sessions' || !this.sessionPreviewRenderEnabled) {
|
|
1330
|
+
return;
|
|
824
1331
|
}
|
|
1332
|
+
const total = Array.isArray(this.activeSessionMessages)
|
|
1333
|
+
? this.activeSessionMessages.length
|
|
1334
|
+
: 0;
|
|
1335
|
+
if (total <= 0) return;
|
|
1336
|
+
const baseSize = Number.isFinite(this.sessionPreviewInitialBatchSize)
|
|
1337
|
+
? Math.max(1, Math.floor(this.sessionPreviewInitialBatchSize))
|
|
1338
|
+
: 40;
|
|
1339
|
+
this.sessionPreviewVisibleCount = Math.min(baseSize, total);
|
|
1340
|
+
this.invalidateSessionTimelineMeasurementCache();
|
|
1341
|
+
},
|
|
1342
|
+
|
|
1343
|
+
async loadMoreSessionMessages(stepSize) {
|
|
1344
|
+
return loadMoreSessionMessagesHelper.call(this, stepSize);
|
|
1345
|
+
},
|
|
1346
|
+
|
|
1347
|
+
suspendSessionTabRender() {
|
|
1348
|
+
this.sessionTabRenderTicket += 1;
|
|
1349
|
+
this.sessionListRenderEnabled = false;
|
|
1350
|
+
this.sessionPreviewRenderEnabled = false;
|
|
1351
|
+
this.cancelSessionTimelineSync();
|
|
1352
|
+
this.sessionTimelineActiveKey = '';
|
|
1353
|
+
this.sessionTimelineLastSyncAt = 0;
|
|
1354
|
+
this.sessionTimelineLastScrollTop = 0;
|
|
1355
|
+
this.sessionTimelineLastAnchorY = 0;
|
|
1356
|
+
this.sessionTimelineLastDirection = 0;
|
|
1357
|
+
this.sessionPreviewScrollEl = null;
|
|
1358
|
+
this.sessionPreviewContainerEl = null;
|
|
1359
|
+
this.sessionPreviewHeaderEl = null;
|
|
1360
|
+
},
|
|
1361
|
+
|
|
1362
|
+
finalizeSessionTabTeardown() {
|
|
1363
|
+
this.resetSessionPreviewMessageRender();
|
|
1364
|
+
this.sessionPreviewPendingVisibleCount = 0;
|
|
1365
|
+
this.clearSessionTimelineRefs();
|
|
1366
|
+
},
|
|
1367
|
+
|
|
1368
|
+
teardownSessionTabRender() {
|
|
1369
|
+
this.suspendSessionTabRender();
|
|
1370
|
+
this.finalizeSessionTabTeardown();
|
|
1371
|
+
},
|
|
1372
|
+
|
|
1373
|
+
prepareSessionTabRender() {
|
|
1374
|
+
const ticket = ++this.sessionTabRenderTicket;
|
|
1375
|
+
this.sessionListRenderEnabled = false;
|
|
1376
|
+
this.sessionPreviewRenderEnabled = false;
|
|
1377
|
+
this.resetSessionPreviewMessageRender();
|
|
1378
|
+
|
|
1379
|
+
this.scheduleAfterFrame(() => {
|
|
1380
|
+
if (ticket !== this.sessionTabRenderTicket || this.mainTab !== 'sessions') {
|
|
1381
|
+
return;
|
|
1382
|
+
}
|
|
1383
|
+
this.sessionListRenderEnabled = true;
|
|
1384
|
+
|
|
1385
|
+
this.scheduleAfterFrame(() => {
|
|
1386
|
+
if (ticket !== this.sessionTabRenderTicket || this.mainTab !== 'sessions') {
|
|
1387
|
+
return;
|
|
1388
|
+
}
|
|
1389
|
+
this.sessionPreviewRenderEnabled = true;
|
|
1390
|
+
this.$nextTick(() => {
|
|
1391
|
+
if (ticket !== this.sessionTabRenderTicket || this.mainTab !== 'sessions') {
|
|
1392
|
+
return;
|
|
1393
|
+
}
|
|
1394
|
+
this.primeSessionPreviewMessageRender();
|
|
1395
|
+
this.updateSessionTimelineOffset();
|
|
1396
|
+
this.scheduleSessionTimelineSync();
|
|
1397
|
+
});
|
|
1398
|
+
});
|
|
1399
|
+
});
|
|
825
1400
|
},
|
|
826
1401
|
|
|
827
1402
|
getSessionStandaloneContext() {
|
|
@@ -868,6 +1443,7 @@ import { createSkillsMethods } from './modules/skills.methods.mjs';
|
|
|
868
1443
|
|
|
869
1444
|
this.sessionStandalone = true;
|
|
870
1445
|
this.mainTab = 'sessions';
|
|
1446
|
+
this.prepareSessionTabRender();
|
|
871
1447
|
|
|
872
1448
|
if (context.error || !context.params) {
|
|
873
1449
|
this.sessionStandaloneError = `会话链接参数不完整:${context.error || '参数解析失败'}`;
|
|
@@ -887,7 +1463,7 @@ import { createSkillsMethods } from './modules/skills.methods.mjs';
|
|
|
887
1463
|
this.activeSessionDetailClipped = false;
|
|
888
1464
|
this.cancelSessionTimelineSync();
|
|
889
1465
|
this.sessionTimelineActiveKey = '';
|
|
890
|
-
this.
|
|
1466
|
+
this.clearSessionTimelineRefs();
|
|
891
1467
|
this.sessionStandaloneError = '';
|
|
892
1468
|
this.sessionStandaloneText = '';
|
|
893
1469
|
this.sessionStandaloneTitle = this.activeSession.title || '会话';
|
|
@@ -1239,7 +1815,7 @@ import { createSkillsMethods } from './modules/skills.methods.mjs';
|
|
|
1239
1815
|
}
|
|
1240
1816
|
this.sessionDeleting[key] = true;
|
|
1241
1817
|
try {
|
|
1242
|
-
const res = await api('
|
|
1818
|
+
const res = await api('trash-session', {
|
|
1243
1819
|
source: session.source,
|
|
1244
1820
|
sessionId: session.sessionId,
|
|
1245
1821
|
filePath: session.filePath
|
|
@@ -1248,8 +1824,29 @@ import { createSkillsMethods } from './modules/skills.methods.mjs';
|
|
|
1248
1824
|
this.showMessage(res.error, 'error');
|
|
1249
1825
|
return;
|
|
1250
1826
|
}
|
|
1251
|
-
this.
|
|
1252
|
-
|
|
1827
|
+
this.removeSessionPin(session);
|
|
1828
|
+
this.invalidateSessionTrashRequests();
|
|
1829
|
+
this.showMessage('已移入回收站', 'success');
|
|
1830
|
+
if (this.sessionTrashLoadedOnce) {
|
|
1831
|
+
this.prependSessionTrashItem(this.buildSessionTrashItemFromSession(session, res), {
|
|
1832
|
+
totalCount: res && res.totalCount !== undefined ? res.totalCount : undefined
|
|
1833
|
+
});
|
|
1834
|
+
} else if (this.sessionTrashCountLoadedOnce) {
|
|
1835
|
+
this.sessionTrashTotalCount = this.normalizeSessionTrashTotalCount(
|
|
1836
|
+
res && res.totalCount !== undefined
|
|
1837
|
+
? res.totalCount
|
|
1838
|
+
: (this.normalizeSessionTrashTotalCount(this.sessionTrashTotalCount, this.sessionTrashItems) + 1),
|
|
1839
|
+
this.sessionTrashItems
|
|
1840
|
+
);
|
|
1841
|
+
} else {
|
|
1842
|
+
this.sessionTrashTotalCount = this.normalizeSessionTrashTotalCount(
|
|
1843
|
+
res && res.totalCount !== undefined
|
|
1844
|
+
? res.totalCount
|
|
1845
|
+
: (this.normalizeSessionTrashTotalCount(this.sessionTrashTotalCount, this.sessionTrashItems) + 1),
|
|
1846
|
+
this.sessionTrashItems
|
|
1847
|
+
);
|
|
1848
|
+
}
|
|
1849
|
+
await this.removeSessionFromCurrentList(session);
|
|
1253
1850
|
} catch (e) {
|
|
1254
1851
|
this.showMessage('删除失败', 'error');
|
|
1255
1852
|
} finally {
|
|
@@ -1257,6 +1854,413 @@ import { createSkillsMethods } from './modules/skills.methods.mjs';
|
|
|
1257
1854
|
}
|
|
1258
1855
|
},
|
|
1259
1856
|
|
|
1857
|
+
buildSessionTrashItemFromSession(session, result = {}) {
|
|
1858
|
+
const deletedAt = typeof result.deletedAt === 'string' && result.deletedAt
|
|
1859
|
+
? result.deletedAt
|
|
1860
|
+
: new Date().toISOString();
|
|
1861
|
+
const source = session && session.source === 'claude' ? 'claude' : 'codex';
|
|
1862
|
+
return {
|
|
1863
|
+
trashId: typeof result.trashId === 'string' ? result.trashId : '',
|
|
1864
|
+
source,
|
|
1865
|
+
sourceLabel: session && typeof session.sourceLabel === 'string' && session.sourceLabel
|
|
1866
|
+
? session.sourceLabel
|
|
1867
|
+
: (source === 'claude' ? 'Claude Code' : 'Codex'),
|
|
1868
|
+
sessionId: session && typeof session.sessionId === 'string' ? session.sessionId : '',
|
|
1869
|
+
title: session && typeof session.title === 'string' && session.title
|
|
1870
|
+
? session.title
|
|
1871
|
+
: (session && typeof session.sessionId === 'string' ? session.sessionId : ''),
|
|
1872
|
+
cwd: session && typeof session.cwd === 'string' ? session.cwd : '',
|
|
1873
|
+
createdAt: session && typeof session.createdAt === 'string' ? session.createdAt : '',
|
|
1874
|
+
updatedAt: session && typeof session.updatedAt === 'string' ? session.updatedAt : '',
|
|
1875
|
+
deletedAt,
|
|
1876
|
+
messageCount: Number.isFinite(Number(result && result.messageCount))
|
|
1877
|
+
? Math.max(0, Math.floor(Number(result.messageCount)))
|
|
1878
|
+
: (Number.isFinite(Number(session && session.messageCount))
|
|
1879
|
+
? Math.max(0, Math.floor(Number(session.messageCount)))
|
|
1880
|
+
: 0),
|
|
1881
|
+
originalFilePath: session && typeof session.filePath === 'string' ? session.filePath : '',
|
|
1882
|
+
provider: session && typeof session.provider === 'string' ? session.provider : source,
|
|
1883
|
+
keywords: Array.isArray(session && session.keywords) ? session.keywords : [],
|
|
1884
|
+
capabilities: session && typeof session.capabilities === 'object' && session.capabilities
|
|
1885
|
+
? session.capabilities
|
|
1886
|
+
: {},
|
|
1887
|
+
claudeIndexPath: '',
|
|
1888
|
+
claudeIndexEntry: null,
|
|
1889
|
+
trashFilePath: ''
|
|
1890
|
+
};
|
|
1891
|
+
},
|
|
1892
|
+
|
|
1893
|
+
prependSessionTrashItem(item, options = {}) {
|
|
1894
|
+
if (!item || !item.trashId) {
|
|
1895
|
+
return;
|
|
1896
|
+
}
|
|
1897
|
+
const existing = Array.isArray(this.sessionTrashItems) ? this.sessionTrashItems : [];
|
|
1898
|
+
const filtered = existing.filter((entry) => this.getSessionTrashActionKey(entry) !== item.trashId);
|
|
1899
|
+
const nextItems = [item, ...filtered].slice(0, SESSION_TRASH_LIST_LIMIT);
|
|
1900
|
+
const previousTotalCount = Number(this.sessionTrashTotalCount);
|
|
1901
|
+
const normalizedPreviousTotal = Number.isFinite(previousTotalCount) && previousTotalCount >= 0
|
|
1902
|
+
? Math.max(existing.length, Math.floor(previousTotalCount))
|
|
1903
|
+
: existing.length;
|
|
1904
|
+
this.sessionTrashItems = nextItems;
|
|
1905
|
+
const previousVisibleCount = Number(this.sessionTrashVisibleCount);
|
|
1906
|
+
const normalizedPreviousVisibleCount = Number.isFinite(previousVisibleCount) && previousVisibleCount > 0
|
|
1907
|
+
? Math.floor(previousVisibleCount)
|
|
1908
|
+
: SESSION_TRASH_PAGE_SIZE;
|
|
1909
|
+
const wasFullyExpanded = normalizedPreviousVisibleCount >= existing.length
|
|
1910
|
+
|| normalizedPreviousVisibleCount >= normalizedPreviousTotal;
|
|
1911
|
+
if (wasFullyExpanded) {
|
|
1912
|
+
this.sessionTrashVisibleCount = Math.min(
|
|
1913
|
+
normalizedPreviousVisibleCount + 1,
|
|
1914
|
+
nextItems.length || (normalizedPreviousVisibleCount + 1)
|
|
1915
|
+
);
|
|
1916
|
+
}
|
|
1917
|
+
const fallbackTotalCount = filtered.length === existing.length
|
|
1918
|
+
? normalizedPreviousTotal + 1
|
|
1919
|
+
: normalizedPreviousTotal;
|
|
1920
|
+
this.sessionTrashTotalCount = this.normalizeSessionTrashTotalCount(
|
|
1921
|
+
options && options.totalCount !== undefined
|
|
1922
|
+
? options.totalCount
|
|
1923
|
+
: fallbackTotalCount,
|
|
1924
|
+
nextItems
|
|
1925
|
+
);
|
|
1926
|
+
},
|
|
1927
|
+
|
|
1928
|
+
normalizeSessionTrashTotalCount(totalCount, fallbackItems = this.sessionTrashItems) {
|
|
1929
|
+
const fallbackCount = Array.isArray(fallbackItems) ? fallbackItems.length : 0;
|
|
1930
|
+
const numericTotal = Number(totalCount);
|
|
1931
|
+
if (!Number.isFinite(numericTotal) || numericTotal < 0) {
|
|
1932
|
+
return fallbackCount;
|
|
1933
|
+
}
|
|
1934
|
+
return Math.floor(numericTotal);
|
|
1935
|
+
},
|
|
1936
|
+
|
|
1937
|
+
getSessionTrashViewState() {
|
|
1938
|
+
if (this.sessionTrashLoading && !this.sessionTrashLoadedOnce) {
|
|
1939
|
+
return 'loading';
|
|
1940
|
+
}
|
|
1941
|
+
const totalCount = Number(this.sessionTrashCount);
|
|
1942
|
+
const normalizedTotalCount = Number.isFinite(totalCount) && totalCount >= 0
|
|
1943
|
+
? Math.floor(totalCount)
|
|
1944
|
+
: 0;
|
|
1945
|
+
const hasVisibleItems = Array.isArray(this.sessionTrashItems) && this.sessionTrashItems.length > 0;
|
|
1946
|
+
if (this.sessionTrashLastLoadFailed && (!this.sessionTrashLoadedOnce || !hasVisibleItems)) {
|
|
1947
|
+
return 'retry';
|
|
1948
|
+
}
|
|
1949
|
+
if (!this.sessionTrashLoadedOnce) {
|
|
1950
|
+
return normalizedTotalCount > 0 ? 'retry' : 'empty';
|
|
1951
|
+
}
|
|
1952
|
+
if (normalizedTotalCount === 0) {
|
|
1953
|
+
return 'empty';
|
|
1954
|
+
}
|
|
1955
|
+
return hasVisibleItems ? 'list' : 'retry';
|
|
1956
|
+
},
|
|
1957
|
+
|
|
1958
|
+
issueSessionTrashCountRequestToken() {
|
|
1959
|
+
const currentToken = Number(this.sessionTrashCountRequestToken);
|
|
1960
|
+
const nextToken = Number.isFinite(currentToken) && currentToken >= 0
|
|
1961
|
+
? Math.floor(currentToken) + 1
|
|
1962
|
+
: 1;
|
|
1963
|
+
this.sessionTrashCountRequestToken = nextToken;
|
|
1964
|
+
return nextToken;
|
|
1965
|
+
},
|
|
1966
|
+
|
|
1967
|
+
issueSessionTrashListRequestToken() {
|
|
1968
|
+
const currentToken = Number(this.sessionTrashListRequestToken);
|
|
1969
|
+
const nextToken = Number.isFinite(currentToken) && currentToken >= 0
|
|
1970
|
+
? Math.floor(currentToken) + 1
|
|
1971
|
+
: 1;
|
|
1972
|
+
this.sessionTrashListRequestToken = nextToken;
|
|
1973
|
+
return nextToken;
|
|
1974
|
+
},
|
|
1975
|
+
|
|
1976
|
+
invalidateSessionTrashRequests() {
|
|
1977
|
+
this.issueSessionTrashCountRequestToken();
|
|
1978
|
+
return this.issueSessionTrashListRequestToken();
|
|
1979
|
+
},
|
|
1980
|
+
|
|
1981
|
+
isLatestSessionTrashCountRequestToken(token) {
|
|
1982
|
+
return Number(token) === Number(this.sessionTrashCountRequestToken);
|
|
1983
|
+
},
|
|
1984
|
+
|
|
1985
|
+
isLatestSessionTrashListRequestToken(token) {
|
|
1986
|
+
return Number(token) === Number(this.sessionTrashListRequestToken);
|
|
1987
|
+
},
|
|
1988
|
+
|
|
1989
|
+
resetSessionTrashVisibleCount() {
|
|
1990
|
+
const totalItems = Array.isArray(this.sessionTrashItems) ? this.sessionTrashItems.length : 0;
|
|
1991
|
+
this.sessionTrashVisibleCount = Math.min(totalItems, SESSION_TRASH_PAGE_SIZE) || SESSION_TRASH_PAGE_SIZE;
|
|
1992
|
+
},
|
|
1993
|
+
|
|
1994
|
+
loadMoreSessionTrashItems() {
|
|
1995
|
+
const totalItems = Array.isArray(this.sessionTrashItems) ? this.sessionTrashItems.length : 0;
|
|
1996
|
+
const visibleCount = Number(this.sessionTrashVisibleCount);
|
|
1997
|
+
const safeVisibleCount = Number.isFinite(visibleCount) && visibleCount > 0
|
|
1998
|
+
? Math.floor(visibleCount)
|
|
1999
|
+
: SESSION_TRASH_PAGE_SIZE;
|
|
2000
|
+
this.sessionTrashVisibleCount = Math.min(totalItems, safeVisibleCount + SESSION_TRASH_PAGE_SIZE);
|
|
2001
|
+
},
|
|
2002
|
+
|
|
2003
|
+
clearActiveSessionState() {
|
|
2004
|
+
this.activeSession = null;
|
|
2005
|
+
this.activeSessionMessages = [];
|
|
2006
|
+
this.resetSessionDetailPagination();
|
|
2007
|
+
this.resetSessionPreviewMessageRender();
|
|
2008
|
+
this.activeSessionDetailError = '';
|
|
2009
|
+
this.activeSessionDetailClipped = false;
|
|
2010
|
+
this.cancelSessionTimelineSync();
|
|
2011
|
+
this.sessionTimelineActiveKey = '';
|
|
2012
|
+
this.clearSessionTimelineRefs();
|
|
2013
|
+
},
|
|
2014
|
+
|
|
2015
|
+
async removeSessionFromCurrentList(session) {
|
|
2016
|
+
const sessionKey = this.getSessionExportKey(session);
|
|
2017
|
+
if (!sessionKey) {
|
|
2018
|
+
return;
|
|
2019
|
+
}
|
|
2020
|
+
const currentList = Array.isArray(this.sessionsList) ? [...this.sessionsList] : [];
|
|
2021
|
+
const removedIndex = currentList.findIndex((item) => this.getSessionExportKey(item) === sessionKey);
|
|
2022
|
+
if (removedIndex < 0) {
|
|
2023
|
+
return;
|
|
2024
|
+
}
|
|
2025
|
+
const activeKey = this.activeSession ? this.getSessionExportKey(this.activeSession) : '';
|
|
2026
|
+
const renderedList = Array.isArray(this.sortedSessionsList) ? this.sortedSessionsList : [];
|
|
2027
|
+
const renderedIndex = renderedList.findIndex((item) => this.getSessionExportKey(item) === sessionKey);
|
|
2028
|
+
let nextActiveKey = '';
|
|
2029
|
+
if (activeKey === sessionKey && renderedIndex >= 0) {
|
|
2030
|
+
const fallbackSession = renderedList[renderedIndex - 1] || renderedList[renderedIndex + 1] || null;
|
|
2031
|
+
nextActiveKey = fallbackSession ? this.getSessionExportKey(fallbackSession) : '';
|
|
2032
|
+
}
|
|
2033
|
+
currentList.splice(removedIndex, 1);
|
|
2034
|
+
this.sessionsList = currentList;
|
|
2035
|
+
this.syncSessionPathOptionsForSource(
|
|
2036
|
+
this.sessionFilterSource,
|
|
2037
|
+
this.extractPathOptionsFromSessions(currentList),
|
|
2038
|
+
false
|
|
2039
|
+
);
|
|
2040
|
+
if (activeKey !== sessionKey) {
|
|
2041
|
+
return;
|
|
2042
|
+
}
|
|
2043
|
+
if (currentList.length === 0) {
|
|
2044
|
+
this.clearActiveSessionState();
|
|
2045
|
+
return;
|
|
2046
|
+
}
|
|
2047
|
+
const nextSession = currentList.find((item) => this.getSessionExportKey(item) === nextActiveKey)
|
|
2048
|
+
|| currentList[Math.min(removedIndex, currentList.length - 1)];
|
|
2049
|
+
if (!nextSession) {
|
|
2050
|
+
this.clearActiveSessionState();
|
|
2051
|
+
return;
|
|
2052
|
+
}
|
|
2053
|
+
await this.selectSession(nextSession);
|
|
2054
|
+
},
|
|
2055
|
+
|
|
2056
|
+
normalizeSettingsTab(tab) {
|
|
2057
|
+
return tab === 'trash' ? 'trash' : 'backup';
|
|
2058
|
+
},
|
|
2059
|
+
|
|
2060
|
+
async onSettingsTabClick(tab) {
|
|
2061
|
+
await this.switchSettingsTab(tab);
|
|
2062
|
+
},
|
|
2063
|
+
|
|
2064
|
+
async switchSettingsTab(tab, options = {}) {
|
|
2065
|
+
const nextTab = this.normalizeSettingsTab(tab);
|
|
2066
|
+
this.settingsTab = nextTab;
|
|
2067
|
+
if (nextTab !== 'trash') {
|
|
2068
|
+
return;
|
|
2069
|
+
}
|
|
2070
|
+
const forceRefresh = options.forceRefresh === true;
|
|
2071
|
+
if (forceRefresh || !this.sessionTrashLoadedOnce) {
|
|
2072
|
+
await this.loadSessionTrash({ forceRefresh });
|
|
2073
|
+
}
|
|
2074
|
+
},
|
|
2075
|
+
|
|
2076
|
+
async loadSessionTrashCount(options = {}) {
|
|
2077
|
+
if (this.sessionTrashCountLoading) {
|
|
2078
|
+
this.sessionTrashCountPendingOptions = {
|
|
2079
|
+
...(this.sessionTrashCountPendingOptions || {}),
|
|
2080
|
+
...(options || {})
|
|
2081
|
+
};
|
|
2082
|
+
return;
|
|
2083
|
+
}
|
|
2084
|
+
const requestToken = this.issueSessionTrashCountRequestToken();
|
|
2085
|
+
this.sessionTrashCountLoading = true;
|
|
2086
|
+
try {
|
|
2087
|
+
const res = await api('list-session-trash', { countOnly: true });
|
|
2088
|
+
if (!this.isLatestSessionTrashCountRequestToken(requestToken)) {
|
|
2089
|
+
return;
|
|
2090
|
+
}
|
|
2091
|
+
if (res.error) {
|
|
2092
|
+
if (options.silent !== true) {
|
|
2093
|
+
this.showMessage(res.error, 'error');
|
|
2094
|
+
}
|
|
2095
|
+
return;
|
|
2096
|
+
}
|
|
2097
|
+
this.sessionTrashTotalCount = this.normalizeSessionTrashTotalCount(
|
|
2098
|
+
res.totalCount,
|
|
2099
|
+
this.sessionTrashItems
|
|
2100
|
+
);
|
|
2101
|
+
this.sessionTrashCountLoadedOnce = true;
|
|
2102
|
+
} catch (e) {
|
|
2103
|
+
if (this.isLatestSessionTrashCountRequestToken(requestToken) && options.silent !== true) {
|
|
2104
|
+
this.showMessage('加载回收站数量失败', 'error');
|
|
2105
|
+
}
|
|
2106
|
+
} finally {
|
|
2107
|
+
this.sessionTrashCountLoading = false;
|
|
2108
|
+
const pendingOptions = this.sessionTrashCountPendingOptions;
|
|
2109
|
+
this.sessionTrashCountPendingOptions = null;
|
|
2110
|
+
if (pendingOptions) {
|
|
2111
|
+
await this.loadSessionTrashCount(pendingOptions);
|
|
2112
|
+
}
|
|
2113
|
+
}
|
|
2114
|
+
},
|
|
2115
|
+
|
|
2116
|
+
getSessionTrashActionKey(item) {
|
|
2117
|
+
return item && typeof item.trashId === 'string' ? item.trashId : '';
|
|
2118
|
+
},
|
|
2119
|
+
|
|
2120
|
+
isSessionTrashActionBusy(item) {
|
|
2121
|
+
const key = typeof item === 'string' ? item : this.getSessionTrashActionKey(item);
|
|
2122
|
+
return !!(key && (this.sessionTrashRestoring[key] || this.sessionTrashPurging[key]));
|
|
2123
|
+
},
|
|
2124
|
+
|
|
2125
|
+
async loadSessionTrash(options = {}) {
|
|
2126
|
+
if (this.sessionTrashLoading) {
|
|
2127
|
+
this.sessionTrashPendingOptions = {
|
|
2128
|
+
...(this.sessionTrashPendingOptions || {}),
|
|
2129
|
+
...(options || {})
|
|
2130
|
+
};
|
|
2131
|
+
return;
|
|
2132
|
+
}
|
|
2133
|
+
const requestToken = this.issueSessionTrashListRequestToken();
|
|
2134
|
+
this.sessionTrashLoading = true;
|
|
2135
|
+
this.sessionTrashLastLoadFailed = false;
|
|
2136
|
+
let loadSucceeded = false;
|
|
2137
|
+
try {
|
|
2138
|
+
const res = await api('list-session-trash', {
|
|
2139
|
+
limit: SESSION_TRASH_LIST_LIMIT,
|
|
2140
|
+
forceRefresh: !!options.forceRefresh
|
|
2141
|
+
});
|
|
2142
|
+
if (!this.isLatestSessionTrashListRequestToken(requestToken)) {
|
|
2143
|
+
return;
|
|
2144
|
+
}
|
|
2145
|
+
if (res.error) {
|
|
2146
|
+
this.sessionTrashLastLoadFailed = true;
|
|
2147
|
+
this.showMessage(res.error, 'error');
|
|
2148
|
+
return;
|
|
2149
|
+
}
|
|
2150
|
+
const nextItems = Array.isArray(res.items) ? res.items : [];
|
|
2151
|
+
this.sessionTrashItems = nextItems;
|
|
2152
|
+
this.resetSessionTrashVisibleCount();
|
|
2153
|
+
this.sessionTrashTotalCount = this.normalizeSessionTrashTotalCount(res.totalCount, nextItems);
|
|
2154
|
+
this.sessionTrashCountLoadedOnce = true;
|
|
2155
|
+
this.sessionTrashLastLoadFailed = false;
|
|
2156
|
+
loadSucceeded = true;
|
|
2157
|
+
} catch (e) {
|
|
2158
|
+
if (this.isLatestSessionTrashListRequestToken(requestToken)) {
|
|
2159
|
+
this.sessionTrashLastLoadFailed = true;
|
|
2160
|
+
this.showMessage('加载回收站失败', 'error');
|
|
2161
|
+
}
|
|
2162
|
+
} finally {
|
|
2163
|
+
this.sessionTrashLoading = false;
|
|
2164
|
+
if (loadSucceeded) {
|
|
2165
|
+
this.sessionTrashLoadedOnce = true;
|
|
2166
|
+
}
|
|
2167
|
+
const pendingOptions = this.sessionTrashPendingOptions;
|
|
2168
|
+
this.sessionTrashPendingOptions = null;
|
|
2169
|
+
if (pendingOptions) {
|
|
2170
|
+
await this.loadSessionTrash(pendingOptions);
|
|
2171
|
+
}
|
|
2172
|
+
}
|
|
2173
|
+
},
|
|
2174
|
+
|
|
2175
|
+
async restoreSessionTrash(item) {
|
|
2176
|
+
const key = this.getSessionTrashActionKey(item);
|
|
2177
|
+
if (!key || this.isSessionTrashActionBusy(key) || this.sessionTrashClearing) {
|
|
2178
|
+
return;
|
|
2179
|
+
}
|
|
2180
|
+
this.sessionTrashRestoring[key] = true;
|
|
2181
|
+
try {
|
|
2182
|
+
const res = await api('restore-session-trash', { trashId: key });
|
|
2183
|
+
if (res.error) {
|
|
2184
|
+
this.showMessage(res.error, 'error');
|
|
2185
|
+
return;
|
|
2186
|
+
}
|
|
2187
|
+
this.showMessage('会话已恢复', 'success');
|
|
2188
|
+
this.invalidateSessionTrashRequests();
|
|
2189
|
+
await this.loadSessionTrash({ forceRefresh: true });
|
|
2190
|
+
if (this.sessionsLoadedOnce) {
|
|
2191
|
+
await this.loadSessions();
|
|
2192
|
+
}
|
|
2193
|
+
} catch (e) {
|
|
2194
|
+
this.showMessage('恢复失败', 'error');
|
|
2195
|
+
} finally {
|
|
2196
|
+
this.sessionTrashRestoring[key] = false;
|
|
2197
|
+
}
|
|
2198
|
+
},
|
|
2199
|
+
|
|
2200
|
+
async purgeSessionTrash(item) {
|
|
2201
|
+
const key = this.getSessionTrashActionKey(item);
|
|
2202
|
+
if (!key || this.isSessionTrashActionBusy(key) || this.sessionTrashClearing) {
|
|
2203
|
+
return;
|
|
2204
|
+
}
|
|
2205
|
+
const confirmed = await this.requestConfirmDialog({
|
|
2206
|
+
title: '彻底删除回收站记录',
|
|
2207
|
+
message: '该会话将从回收站永久删除,且无法恢复。',
|
|
2208
|
+
confirmText: '彻底删除',
|
|
2209
|
+
cancelText: '取消',
|
|
2210
|
+
danger: true
|
|
2211
|
+
});
|
|
2212
|
+
if (!confirmed) {
|
|
2213
|
+
return;
|
|
2214
|
+
}
|
|
2215
|
+
this.sessionTrashPurging[key] = true;
|
|
2216
|
+
try {
|
|
2217
|
+
const res = await api('purge-session-trash', { trashId: key });
|
|
2218
|
+
if (res.error) {
|
|
2219
|
+
this.showMessage(res.error, 'error');
|
|
2220
|
+
return;
|
|
2221
|
+
}
|
|
2222
|
+
this.showMessage('已彻底删除', 'success');
|
|
2223
|
+
this.invalidateSessionTrashRequests();
|
|
2224
|
+
await this.loadSessionTrash({ forceRefresh: true });
|
|
2225
|
+
} catch (e) {
|
|
2226
|
+
this.showMessage('彻底删除失败', 'error');
|
|
2227
|
+
} finally {
|
|
2228
|
+
this.sessionTrashPurging[key] = false;
|
|
2229
|
+
}
|
|
2230
|
+
},
|
|
2231
|
+
|
|
2232
|
+
async clearSessionTrash() {
|
|
2233
|
+
const normalizedCount = Number(this.sessionTrashCount);
|
|
2234
|
+
if (this.sessionTrashClearing || !Number.isFinite(normalizedCount) || normalizedCount <= 0) {
|
|
2235
|
+
return;
|
|
2236
|
+
}
|
|
2237
|
+
const confirmed = await this.requestConfirmDialog({
|
|
2238
|
+
title: '清空回收站',
|
|
2239
|
+
message: '该操作会永久删除回收站中的全部会话,且无法恢复。',
|
|
2240
|
+
confirmText: '全部清空',
|
|
2241
|
+
cancelText: '取消',
|
|
2242
|
+
danger: true
|
|
2243
|
+
});
|
|
2244
|
+
if (!confirmed) {
|
|
2245
|
+
return;
|
|
2246
|
+
}
|
|
2247
|
+
this.sessionTrashClearing = true;
|
|
2248
|
+
try {
|
|
2249
|
+
const res = await api('purge-session-trash', { all: true });
|
|
2250
|
+
if (res.error) {
|
|
2251
|
+
this.showMessage(res.error, 'error');
|
|
2252
|
+
return;
|
|
2253
|
+
}
|
|
2254
|
+
this.showMessage('回收站已清空', 'success');
|
|
2255
|
+
this.invalidateSessionTrashRequests();
|
|
2256
|
+
await this.loadSessionTrash({ forceRefresh: true });
|
|
2257
|
+
} catch (e) {
|
|
2258
|
+
this.showMessage('清空回收站失败', 'error');
|
|
2259
|
+
} finally {
|
|
2260
|
+
this.sessionTrashClearing = false;
|
|
2261
|
+
}
|
|
2262
|
+
},
|
|
2263
|
+
|
|
1260
2264
|
normalizeSessionPathValue(value) {
|
|
1261
2265
|
return normalizeSessionPathFilter(value);
|
|
1262
2266
|
},
|
|
@@ -1384,6 +2388,124 @@ import { createSkillsMethods } from './modules/skills.methods.mjs';
|
|
|
1384
2388
|
localStorage.removeItem('codexmateSessionPathFilter');
|
|
1385
2389
|
}
|
|
1386
2390
|
},
|
|
2391
|
+
normalizeSessionPinnedMap(raw) {
|
|
2392
|
+
if (!raw || typeof raw !== 'object' || Array.isArray(raw)) {
|
|
2393
|
+
return {};
|
|
2394
|
+
}
|
|
2395
|
+
const next = {};
|
|
2396
|
+
for (const [key, value] of Object.entries(raw)) {
|
|
2397
|
+
if (!key) continue;
|
|
2398
|
+
const numeric = Number(value);
|
|
2399
|
+
if (!Number.isFinite(numeric) || numeric <= 0) continue;
|
|
2400
|
+
next[key] = Math.floor(numeric);
|
|
2401
|
+
}
|
|
2402
|
+
return next;
|
|
2403
|
+
},
|
|
2404
|
+
restoreSessionPinnedMap() {
|
|
2405
|
+
const cached = localStorage.getItem('codexmateSessionPinnedMap');
|
|
2406
|
+
if (!cached) {
|
|
2407
|
+
this.sessionPinnedMap = {};
|
|
2408
|
+
return;
|
|
2409
|
+
}
|
|
2410
|
+
try {
|
|
2411
|
+
const parsed = JSON.parse(cached);
|
|
2412
|
+
this.sessionPinnedMap = this.normalizeSessionPinnedMap(parsed);
|
|
2413
|
+
} catch (_) {
|
|
2414
|
+
this.sessionPinnedMap = {};
|
|
2415
|
+
localStorage.removeItem('codexmateSessionPinnedMap');
|
|
2416
|
+
}
|
|
2417
|
+
},
|
|
2418
|
+
persistSessionPinnedMap() {
|
|
2419
|
+
const payload = (this.sessionPinnedMap && typeof this.sessionPinnedMap === 'object')
|
|
2420
|
+
? this.sessionPinnedMap
|
|
2421
|
+
: {};
|
|
2422
|
+
localStorage.setItem('codexmateSessionPinnedMap', JSON.stringify(payload));
|
|
2423
|
+
},
|
|
2424
|
+
shouldPruneSessionPinnedMap(sessions = this.sessionsList) {
|
|
2425
|
+
if (!Array.isArray(sessions) || sessions.length === 0) {
|
|
2426
|
+
return false;
|
|
2427
|
+
}
|
|
2428
|
+
if (this.sessionFilterSource !== 'all') {
|
|
2429
|
+
return false;
|
|
2430
|
+
}
|
|
2431
|
+
if (this.sessionPathFilter) {
|
|
2432
|
+
return false;
|
|
2433
|
+
}
|
|
2434
|
+
if (this.sessionQuery && isSessionQueryEnabled(this.sessionFilterSource)) {
|
|
2435
|
+
return false;
|
|
2436
|
+
}
|
|
2437
|
+
if (this.sessionRoleFilter && this.sessionRoleFilter !== 'all') {
|
|
2438
|
+
return false;
|
|
2439
|
+
}
|
|
2440
|
+
if (this.sessionTimePreset && this.sessionTimePreset !== 'all') {
|
|
2441
|
+
return false;
|
|
2442
|
+
}
|
|
2443
|
+
return true;
|
|
2444
|
+
},
|
|
2445
|
+
pruneSessionPinnedMap(sessions = this.sessionsList) {
|
|
2446
|
+
const current = (this.sessionPinnedMap && typeof this.sessionPinnedMap === 'object')
|
|
2447
|
+
? this.sessionPinnedMap
|
|
2448
|
+
: {};
|
|
2449
|
+
const list = Array.isArray(sessions) ? sessions : [];
|
|
2450
|
+
if (Object.keys(current).length === 0 || !this.shouldPruneSessionPinnedMap(list)) {
|
|
2451
|
+
return;
|
|
2452
|
+
}
|
|
2453
|
+
const validKeys = new Set(list.map((session) => this.getSessionExportKey(session)).filter(Boolean));
|
|
2454
|
+
const next = {};
|
|
2455
|
+
let changed = false;
|
|
2456
|
+
for (const [key, value] of Object.entries(current)) {
|
|
2457
|
+
if (!validKeys.has(key)) {
|
|
2458
|
+
changed = true;
|
|
2459
|
+
continue;
|
|
2460
|
+
}
|
|
2461
|
+
next[key] = value;
|
|
2462
|
+
}
|
|
2463
|
+
if (!changed) {
|
|
2464
|
+
return;
|
|
2465
|
+
}
|
|
2466
|
+
this.sessionPinnedMap = next;
|
|
2467
|
+
this.persistSessionPinnedMap();
|
|
2468
|
+
},
|
|
2469
|
+
getSessionPinTimestamp(session) {
|
|
2470
|
+
if (!session) return 0;
|
|
2471
|
+
const key = this.getSessionExportKey(session);
|
|
2472
|
+
if (!key) return 0;
|
|
2473
|
+
const raw = this.sessionPinnedMap && this.sessionPinnedMap[key];
|
|
2474
|
+
const numeric = Number(raw);
|
|
2475
|
+
return Number.isFinite(numeric) && numeric > 0 ? Math.floor(numeric) : 0;
|
|
2476
|
+
},
|
|
2477
|
+
isSessionPinned(session) {
|
|
2478
|
+
return this.getSessionPinTimestamp(session) > 0;
|
|
2479
|
+
},
|
|
2480
|
+
toggleSessionPin(session) {
|
|
2481
|
+
if (!session) return;
|
|
2482
|
+
const key = this.getSessionExportKey(session);
|
|
2483
|
+
if (!key) return;
|
|
2484
|
+
const current = (this.sessionPinnedMap && typeof this.sessionPinnedMap === 'object')
|
|
2485
|
+
? this.sessionPinnedMap
|
|
2486
|
+
: {};
|
|
2487
|
+
const next = { ...current };
|
|
2488
|
+
if (next[key]) {
|
|
2489
|
+
delete next[key];
|
|
2490
|
+
} else {
|
|
2491
|
+
next[key] = Date.now();
|
|
2492
|
+
}
|
|
2493
|
+
this.sessionPinnedMap = next;
|
|
2494
|
+
this.persistSessionPinnedMap();
|
|
2495
|
+
},
|
|
2496
|
+
removeSessionPin(session) {
|
|
2497
|
+
if (!session) return;
|
|
2498
|
+
const key = this.getSessionExportKey(session);
|
|
2499
|
+
if (!key) return;
|
|
2500
|
+
const current = (this.sessionPinnedMap && typeof this.sessionPinnedMap === 'object')
|
|
2501
|
+
? this.sessionPinnedMap
|
|
2502
|
+
: {};
|
|
2503
|
+
if (!current[key]) return;
|
|
2504
|
+
const next = { ...current };
|
|
2505
|
+
delete next[key];
|
|
2506
|
+
this.sessionPinnedMap = next;
|
|
2507
|
+
this.persistSessionPinnedMap();
|
|
2508
|
+
},
|
|
1387
2509
|
|
|
1388
2510
|
async onSessionSourceChange() {
|
|
1389
2511
|
this.refreshSessionPathOptions(this.sessionFilterSource);
|
|
@@ -1434,13 +2556,137 @@ import { createSkillsMethods } from './modules/skills.methods.mjs';
|
|
|
1434
2556
|
},
|
|
1435
2557
|
setSessionPreviewScrollRef(el) {
|
|
1436
2558
|
this.sessionPreviewScrollEl = el || null;
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
2559
|
+
this.invalidateSessionTimelineMeasurementCache();
|
|
2560
|
+
const shouldSync = !!(
|
|
2561
|
+
this.sessionPreviewScrollEl
|
|
2562
|
+
&& this.mainTab === 'sessions'
|
|
2563
|
+
&& this.getMainTabForNav() === 'sessions'
|
|
2564
|
+
&& this.sessionPreviewRenderEnabled
|
|
2565
|
+
&& this.sessionTimelineNodes.length
|
|
2566
|
+
);
|
|
2567
|
+
if (!shouldSync) {
|
|
1440
2568
|
this.cancelSessionTimelineSync();
|
|
2569
|
+
this.updateSessionTimelineOffset();
|
|
2570
|
+
return;
|
|
1441
2571
|
}
|
|
2572
|
+
const boundScrollEl = this.sessionPreviewScrollEl;
|
|
2573
|
+
this.$nextTick(() => {
|
|
2574
|
+
if (this.sessionPreviewScrollEl !== boundScrollEl) return;
|
|
2575
|
+
if (this.mainTab !== 'sessions' || !this.sessionPreviewRenderEnabled) return;
|
|
2576
|
+
if (!this.sessionTimelineNodes.length) return;
|
|
2577
|
+
this.scheduleSessionTimelineSync();
|
|
2578
|
+
});
|
|
1442
2579
|
this.updateSessionTimelineOffset();
|
|
1443
2580
|
},
|
|
2581
|
+
clearSessionTimelineRefs() {
|
|
2582
|
+
this.sessionMessageRefMap = Object.create(null);
|
|
2583
|
+
this.sessionMessageRefBinderMap = Object.create(null);
|
|
2584
|
+
this.sessionTimelineLastAnchorY = 0;
|
|
2585
|
+
this.sessionTimelineLastDirection = 0;
|
|
2586
|
+
this.invalidateSessionTimelineMeasurementCache(true);
|
|
2587
|
+
},
|
|
2588
|
+
ensureSessionTimelineMeasurementCache() {
|
|
2589
|
+
if (this.__sessionTimelineMeasurementCache) {
|
|
2590
|
+
return this.__sessionTimelineMeasurementCache;
|
|
2591
|
+
}
|
|
2592
|
+
this.__sessionTimelineMeasurementCache = {
|
|
2593
|
+
offsetByKey: Object.create(null),
|
|
2594
|
+
dirty: true
|
|
2595
|
+
};
|
|
2596
|
+
return this.__sessionTimelineMeasurementCache;
|
|
2597
|
+
},
|
|
2598
|
+
invalidateSessionTimelineMeasurementCache(resetOffset = false) {
|
|
2599
|
+
const cache = this.ensureSessionTimelineMeasurementCache();
|
|
2600
|
+
if (resetOffset) {
|
|
2601
|
+
cache.offsetByKey = Object.create(null);
|
|
2602
|
+
}
|
|
2603
|
+
cache.dirty = true;
|
|
2604
|
+
},
|
|
2605
|
+
refreshSessionTimelineMeasurementCache(nodes = null) {
|
|
2606
|
+
const cache = this.ensureSessionTimelineMeasurementCache();
|
|
2607
|
+
const nodeList = Array.isArray(nodes) ? nodes : (Array.isArray(this.sessionTimelineNodes) ? this.sessionTimelineNodes : []);
|
|
2608
|
+
if (!nodeList.length) {
|
|
2609
|
+
cache.offsetByKey = Object.create(null);
|
|
2610
|
+
cache.dirty = false;
|
|
2611
|
+
return cache.offsetByKey;
|
|
2612
|
+
}
|
|
2613
|
+
const scrollEl = this.sessionPreviewScrollEl || this.$refs.sessionPreviewScroll;
|
|
2614
|
+
const scrollRect = scrollEl && typeof scrollEl.getBoundingClientRect === 'function'
|
|
2615
|
+
? scrollEl.getBoundingClientRect()
|
|
2616
|
+
: null;
|
|
2617
|
+
const scrollTop = scrollEl ? Number(scrollEl.scrollTop || 0) : 0;
|
|
2618
|
+
const nextOffsetByKey = Object.create(null);
|
|
2619
|
+
for (const node of nodeList) {
|
|
2620
|
+
if (!node || !node.key) continue;
|
|
2621
|
+
const messageEl = this.sessionMessageRefMap[node.key];
|
|
2622
|
+
if (!messageEl) continue;
|
|
2623
|
+
let top = Number.NaN;
|
|
2624
|
+
if (
|
|
2625
|
+
scrollRect
|
|
2626
|
+
&& typeof messageEl.getBoundingClientRect === 'function'
|
|
2627
|
+
) {
|
|
2628
|
+
const messageRect = messageEl.getBoundingClientRect();
|
|
2629
|
+
top = scrollTop + (messageRect.top - scrollRect.top);
|
|
2630
|
+
} else {
|
|
2631
|
+
top = Number(messageEl.offsetTop || 0);
|
|
2632
|
+
}
|
|
2633
|
+
if (!Number.isFinite(top)) continue;
|
|
2634
|
+
nextOffsetByKey[node.key] = top;
|
|
2635
|
+
}
|
|
2636
|
+
cache.offsetByKey = nextOffsetByKey;
|
|
2637
|
+
cache.dirty = false;
|
|
2638
|
+
return cache.offsetByKey;
|
|
2639
|
+
},
|
|
2640
|
+
getCachedSessionTimelineMeasuredNodes(nodes) {
|
|
2641
|
+
const nodeList = Array.isArray(nodes) ? nodes : [];
|
|
2642
|
+
if (!nodeList.length) {
|
|
2643
|
+
return [];
|
|
2644
|
+
}
|
|
2645
|
+
const cache = this.ensureSessionTimelineMeasurementCache();
|
|
2646
|
+
if (cache.dirty) {
|
|
2647
|
+
this.refreshSessionTimelineMeasurementCache(nodeList);
|
|
2648
|
+
}
|
|
2649
|
+
const offsetByKey = cache.offsetByKey || Object.create(null);
|
|
2650
|
+
const measuredNodes = [];
|
|
2651
|
+
for (const node of nodeList) {
|
|
2652
|
+
if (!node || !node.key) continue;
|
|
2653
|
+
const top = Number(offsetByKey[node.key]);
|
|
2654
|
+
if (!Number.isFinite(top)) continue;
|
|
2655
|
+
measuredNodes.push({
|
|
2656
|
+
key: node.key,
|
|
2657
|
+
top
|
|
2658
|
+
});
|
|
2659
|
+
}
|
|
2660
|
+
if (measuredNodes.length >= nodeList.length) {
|
|
2661
|
+
return measuredNodes;
|
|
2662
|
+
}
|
|
2663
|
+
const refreshedOffsetByKey = this.refreshSessionTimelineMeasurementCache(nodeList);
|
|
2664
|
+
const refreshedNodes = [];
|
|
2665
|
+
for (const node of nodeList) {
|
|
2666
|
+
if (!node || !node.key) continue;
|
|
2667
|
+
const top = Number(refreshedOffsetByKey[node.key]);
|
|
2668
|
+
if (!Number.isFinite(top)) continue;
|
|
2669
|
+
refreshedNodes.push({
|
|
2670
|
+
key: node.key,
|
|
2671
|
+
top
|
|
2672
|
+
});
|
|
2673
|
+
}
|
|
2674
|
+
return refreshedNodes;
|
|
2675
|
+
},
|
|
2676
|
+
getSessionMessageRefBinder(messageKey) {
|
|
2677
|
+
if (!this.isSessionTimelineNodeKey(messageKey)) return null;
|
|
2678
|
+
const current = this.sessionMessageRefBinderMap[messageKey];
|
|
2679
|
+
if (!current || current.ticket !== this.sessionTabRenderTicket) {
|
|
2680
|
+
const ticket = this.sessionTabRenderTicket;
|
|
2681
|
+
this.sessionMessageRefBinderMap[messageKey] = {
|
|
2682
|
+
ticket,
|
|
2683
|
+
bind: (el) => {
|
|
2684
|
+
this.bindSessionMessageRef(messageKey, el, ticket);
|
|
2685
|
+
}
|
|
2686
|
+
};
|
|
2687
|
+
}
|
|
2688
|
+
return this.sessionMessageRefBinderMap[messageKey].bind;
|
|
2689
|
+
},
|
|
1444
2690
|
updateSessionTimelineOffset() {
|
|
1445
2691
|
const container = this.sessionPreviewContainerEl || this.$refs.sessionPreviewContainer;
|
|
1446
2692
|
if (!container || !container.style) return;
|
|
@@ -1451,12 +2697,39 @@ import { createSkillsMethods } from './modules/skills.methods.mjs';
|
|
|
1451
2697
|
const offset = headerHeight > 0 ? (headerHeight + 12) : 72;
|
|
1452
2698
|
container.style.setProperty('--session-preview-header-offset', `${offset}px`);
|
|
1453
2699
|
},
|
|
1454
|
-
bindSessionMessageRef(messageKey, el) {
|
|
2700
|
+
bindSessionMessageRef(messageKey, el, ticket = this.sessionTabRenderTicket) {
|
|
2701
|
+
if (!this.sessionTimelineEnabled) return;
|
|
1455
2702
|
if (!messageKey) return;
|
|
2703
|
+
if (ticket !== this.sessionTabRenderTicket) return;
|
|
1456
2704
|
if (el) {
|
|
2705
|
+
if (!this.isSessionTimelineNodeKey(messageKey)) return;
|
|
2706
|
+
if (this.sessionMessageRefMap[messageKey] === el) return;
|
|
1457
2707
|
this.sessionMessageRefMap[messageKey] = el;
|
|
2708
|
+
this.invalidateSessionTimelineMeasurementCache();
|
|
1458
2709
|
} else {
|
|
2710
|
+
if (!this.sessionMessageRefMap[messageKey]) return;
|
|
1459
2711
|
delete this.sessionMessageRefMap[messageKey];
|
|
2712
|
+
this.invalidateSessionTimelineMeasurementCache();
|
|
2713
|
+
}
|
|
2714
|
+
},
|
|
2715
|
+
isSessionTimelineNodeKey(messageKey) {
|
|
2716
|
+
if (!messageKey) return false;
|
|
2717
|
+
return !!(this.sessionTimelineNodeKeyMap && this.sessionTimelineNodeKeyMap[messageKey]);
|
|
2718
|
+
},
|
|
2719
|
+
pruneSessionMessageRefs() {
|
|
2720
|
+
const nodeKeyMap = this.sessionTimelineNodeKeyMap || Object.create(null);
|
|
2721
|
+
let removed = false;
|
|
2722
|
+
for (const key of Object.keys(this.sessionMessageRefMap)) {
|
|
2723
|
+
if (nodeKeyMap[key]) continue;
|
|
2724
|
+
delete this.sessionMessageRefMap[key];
|
|
2725
|
+
removed = true;
|
|
2726
|
+
}
|
|
2727
|
+
for (const key of Object.keys(this.sessionMessageRefBinderMap)) {
|
|
2728
|
+
if (nodeKeyMap[key]) continue;
|
|
2729
|
+
delete this.sessionMessageRefBinderMap[key];
|
|
2730
|
+
}
|
|
2731
|
+
if (removed) {
|
|
2732
|
+
this.invalidateSessionTimelineMeasurementCache();
|
|
1460
2733
|
}
|
|
1461
2734
|
},
|
|
1462
2735
|
cancelSessionTimelineSync() {
|
|
@@ -1478,11 +2751,39 @@ import { createSkillsMethods } from './modules/skills.methods.mjs';
|
|
|
1478
2751
|
this.syncSessionTimelineActiveFromScroll();
|
|
1479
2752
|
},
|
|
1480
2753
|
onSessionPreviewScroll() {
|
|
2754
|
+
if (
|
|
2755
|
+
!this.sessionTimelineEnabled
|
|
2756
|
+
|| this.mainTab !== 'sessions'
|
|
2757
|
+
|| this.getMainTabForNav() !== 'sessions'
|
|
2758
|
+
|| !this.sessionPreviewRenderEnabled
|
|
2759
|
+
) return;
|
|
2760
|
+
if (!this.sessionTimelineNodes.length) return;
|
|
2761
|
+
const scrollEl = this.sessionPreviewScrollEl || this.$refs.sessionPreviewScroll;
|
|
2762
|
+
if (!scrollEl) return;
|
|
2763
|
+
const now = Date.now();
|
|
2764
|
+
const currentTop = Number(scrollEl.scrollTop || 0);
|
|
2765
|
+
const delta = Math.abs(currentTop - Number(this.sessionTimelineLastScrollTop || 0));
|
|
2766
|
+
const elapsed = now - Number(this.sessionTimelineLastSyncAt || 0);
|
|
2767
|
+
if (delta < 48 && elapsed < 120) {
|
|
2768
|
+
return;
|
|
2769
|
+
}
|
|
2770
|
+
this.sessionTimelineLastScrollTop = currentTop;
|
|
2771
|
+
this.sessionTimelineLastSyncAt = now;
|
|
1481
2772
|
this.scheduleSessionTimelineSync();
|
|
1482
2773
|
},
|
|
1483
2774
|
onWindowResize() {
|
|
1484
2775
|
this.updateCompactLayoutMode();
|
|
2776
|
+
if (
|
|
2777
|
+
!this.sessionTimelineEnabled
|
|
2778
|
+
|| this.mainTab !== 'sessions'
|
|
2779
|
+
|| this.getMainTabForNav() !== 'sessions'
|
|
2780
|
+
|| !this.sessionPreviewRenderEnabled
|
|
2781
|
+
) {
|
|
2782
|
+
return;
|
|
2783
|
+
}
|
|
2784
|
+
if (!this.sessionTimelineNodes.length) return;
|
|
1485
2785
|
this.updateSessionTimelineOffset();
|
|
2786
|
+
this.invalidateSessionTimelineMeasurementCache();
|
|
1486
2787
|
this.scheduleSessionTimelineSync();
|
|
1487
2788
|
},
|
|
1488
2789
|
shouldForceCompactLayout() {
|
|
@@ -1535,35 +2836,93 @@ import { createSkillsMethods } from './modules/skills.methods.mjs';
|
|
|
1535
2836
|
this.applyCompactLayoutClass(enabled);
|
|
1536
2837
|
},
|
|
1537
2838
|
syncSessionTimelineActiveFromScroll() {
|
|
2839
|
+
if (
|
|
2840
|
+
!this.sessionTimelineEnabled
|
|
2841
|
+
|| this.mainTab !== 'sessions'
|
|
2842
|
+
|| this.getMainTabForNav() !== 'sessions'
|
|
2843
|
+
|| !this.sessionPreviewRenderEnabled
|
|
2844
|
+
) {
|
|
2845
|
+
if (this.sessionTimelineActiveKey) {
|
|
2846
|
+
this.sessionTimelineActiveKey = '';
|
|
2847
|
+
}
|
|
2848
|
+
return;
|
|
2849
|
+
}
|
|
1538
2850
|
const nodes = Array.isArray(this.sessionTimelineNodes) ? this.sessionTimelineNodes : [];
|
|
1539
2851
|
if (!nodes.length) {
|
|
1540
|
-
this.sessionTimelineActiveKey
|
|
2852
|
+
if (this.sessionTimelineActiveKey) {
|
|
2853
|
+
this.sessionTimelineActiveKey = '';
|
|
2854
|
+
}
|
|
1541
2855
|
return;
|
|
1542
2856
|
}
|
|
2857
|
+
this.pruneSessionMessageRefs();
|
|
1543
2858
|
const scrollEl = this.sessionPreviewScrollEl || this.$refs.sessionPreviewScroll;
|
|
1544
2859
|
if (!scrollEl) {
|
|
1545
|
-
this.sessionTimelineActiveKey
|
|
2860
|
+
if (!this.isSessionTimelineNodeKey(this.sessionTimelineActiveKey)) {
|
|
2861
|
+
const fallbackKey = nodes[0].key;
|
|
2862
|
+
if (this.sessionTimelineActiveKey !== fallbackKey) {
|
|
2863
|
+
this.sessionTimelineActiveKey = fallbackKey;
|
|
2864
|
+
}
|
|
2865
|
+
}
|
|
1546
2866
|
return;
|
|
1547
2867
|
}
|
|
1548
|
-
const
|
|
1549
|
-
const
|
|
1550
|
-
const
|
|
1551
|
-
const
|
|
1552
|
-
let
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
2868
|
+
const headerEl = scrollEl.querySelector('.session-preview-header');
|
|
2869
|
+
const stickyOffset = headerEl ? (headerEl.offsetHeight + 8) : 8;
|
|
2870
|
+
const rawAnchorY = Number(scrollEl.scrollTop || 0) + stickyOffset;
|
|
2871
|
+
const previousAnchorY = Number(this.sessionTimelineLastAnchorY || 0);
|
|
2872
|
+
let direction = rawAnchorY - previousAnchorY;
|
|
2873
|
+
if (Math.abs(direction) < 1) {
|
|
2874
|
+
direction = Number(this.sessionTimelineLastDirection || 0);
|
|
2875
|
+
} else {
|
|
2876
|
+
this.sessionTimelineLastDirection = direction > 0 ? 1 : -1;
|
|
2877
|
+
}
|
|
2878
|
+
this.sessionTimelineLastAnchorY = rawAnchorY;
|
|
2879
|
+
const hysteresisPx = 18;
|
|
2880
|
+
const hysteresis = direction > 0 ? -hysteresisPx : (direction < 0 ? hysteresisPx : 0);
|
|
2881
|
+
const anchorY = rawAnchorY + hysteresis;
|
|
2882
|
+
const measuredNodes = this.getCachedSessionTimelineMeasuredNodes(nodes);
|
|
2883
|
+
if (!measuredNodes.length) {
|
|
2884
|
+
if (!this.isSessionTimelineNodeKey(this.sessionTimelineActiveKey)) {
|
|
2885
|
+
this.sessionTimelineActiveKey = nodes[0].key;
|
|
2886
|
+
}
|
|
2887
|
+
return;
|
|
2888
|
+
}
|
|
2889
|
+
let low = 0;
|
|
2890
|
+
let high = measuredNodes.length - 1;
|
|
2891
|
+
let candidateIndex = 0;
|
|
2892
|
+
while (low <= high) {
|
|
2893
|
+
const mid = Math.floor((low + high) / 2);
|
|
2894
|
+
if (measuredNodes[mid].top <= anchorY) {
|
|
2895
|
+
candidateIndex = mid;
|
|
2896
|
+
low = mid + 1;
|
|
2897
|
+
} else {
|
|
2898
|
+
high = mid - 1;
|
|
2899
|
+
}
|
|
2900
|
+
}
|
|
2901
|
+
let currentIndex = -1;
|
|
2902
|
+
if (this.sessionTimelineActiveKey) {
|
|
2903
|
+
for (let i = 0; i < measuredNodes.length; i += 1) {
|
|
2904
|
+
if (measuredNodes[i].key === this.sessionTimelineActiveKey) {
|
|
2905
|
+
currentIndex = i;
|
|
2906
|
+
break;
|
|
2907
|
+
}
|
|
1560
2908
|
}
|
|
1561
|
-
break;
|
|
1562
2909
|
}
|
|
1563
|
-
|
|
2910
|
+
if (currentIndex >= 0) {
|
|
2911
|
+
if (direction > 0 && candidateIndex < currentIndex) {
|
|
2912
|
+
candidateIndex = currentIndex;
|
|
2913
|
+
} else if (direction < 0 && candidateIndex > currentIndex) {
|
|
2914
|
+
candidateIndex = currentIndex;
|
|
2915
|
+
}
|
|
2916
|
+
}
|
|
2917
|
+
const activeKey = measuredNodes[candidateIndex].key;
|
|
2918
|
+
if (this.sessionTimelineActiveKey !== activeKey) {
|
|
2919
|
+
this.sessionTimelineActiveKey = activeKey;
|
|
2920
|
+
}
|
|
1564
2921
|
},
|
|
1565
2922
|
jumpToSessionTimelineNode(messageKey) {
|
|
2923
|
+
if (!this.sessionTimelineEnabled || this.mainTab !== 'sessions' || !this.sessionPreviewRenderEnabled) return;
|
|
1566
2924
|
if (!messageKey) return;
|
|
2925
|
+
if (!this.isSessionTimelineNodeKey(messageKey)) return;
|
|
1567
2926
|
const scrollEl = this.sessionPreviewScrollEl || this.$refs.sessionPreviewScroll;
|
|
1568
2927
|
if (!scrollEl) return;
|
|
1569
2928
|
const messageEl = this.sessionMessageRefMap[messageKey];
|
|
@@ -1636,67 +2995,9 @@ import { createSkillsMethods } from './modules/skills.methods.mjs';
|
|
|
1636
2995
|
},
|
|
1637
2996
|
|
|
1638
2997
|
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
|
-
}
|
|
2998
|
+
const result = await loadSessionsHelper.call(this, api);
|
|
2999
|
+
this.pruneSessionPinnedMap(this.sessionsList);
|
|
3000
|
+
return result;
|
|
1700
3001
|
},
|
|
1701
3002
|
|
|
1702
3003
|
async selectSession(session) {
|
|
@@ -1704,11 +3005,13 @@ import { createSkillsMethods } from './modules/skills.methods.mjs';
|
|
|
1704
3005
|
if (this.activeSession && this.getSessionExportKey(this.activeSession) === this.getSessionExportKey(session)) return;
|
|
1705
3006
|
this.activeSession = session;
|
|
1706
3007
|
this.activeSessionMessages = [];
|
|
3008
|
+
this.resetSessionDetailPagination();
|
|
3009
|
+
this.resetSessionPreviewMessageRender();
|
|
1707
3010
|
this.activeSessionDetailError = '';
|
|
1708
3011
|
this.activeSessionDetailClipped = false;
|
|
1709
3012
|
this.cancelSessionTimelineSync();
|
|
1710
3013
|
this.sessionTimelineActiveKey = '';
|
|
1711
|
-
this.
|
|
3014
|
+
this.clearSessionTimelineRefs();
|
|
1712
3015
|
await this.loadActiveSessionDetail();
|
|
1713
3016
|
},
|
|
1714
3017
|
|
|
@@ -1757,87 +3060,8 @@ import { createSkillsMethods } from './modules/skills.methods.mjs';
|
|
|
1757
3060
|
}
|
|
1758
3061
|
},
|
|
1759
3062
|
|
|
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
|
-
}
|
|
3063
|
+
async loadActiveSessionDetail(options = {}) {
|
|
3064
|
+
return loadActiveSessionDetailHelper.call(this, api, options);
|
|
1841
3065
|
},
|
|
1842
3066
|
|
|
1843
3067
|
downloadTextFile(fileName, content, mimeType = 'text/markdown;charset=utf-8') {
|
|
@@ -2142,9 +3366,11 @@ import { createSkillsMethods } from './modules/skills.methods.mjs';
|
|
|
2142
3366
|
return;
|
|
2143
3367
|
}
|
|
2144
3368
|
this.agentsContent = res.content || '';
|
|
3369
|
+
this.agentsOriginalContent = this.agentsContent;
|
|
2145
3370
|
this.agentsPath = res.path || '';
|
|
2146
3371
|
this.agentsExists = !!res.exists;
|
|
2147
3372
|
this.agentsLineEnding = res.lineEnding === '\r\n' ? '\r\n' : '\n';
|
|
3373
|
+
this.resetAgentsDiffState();
|
|
2148
3374
|
this.showAgentsModal = true;
|
|
2149
3375
|
} catch (e) {
|
|
2150
3376
|
this.showMessage('加载文件失败', 'error');
|
|
@@ -2168,9 +3394,11 @@ import { createSkillsMethods } from './modules/skills.methods.mjs';
|
|
|
2168
3394
|
this.showMessage(`OpenClaw 配置解析失败,已使用默认 Workspace:${res.configError}`, 'error');
|
|
2169
3395
|
}
|
|
2170
3396
|
this.agentsContent = res.content || '';
|
|
3397
|
+
this.agentsOriginalContent = this.agentsContent;
|
|
2171
3398
|
this.agentsPath = res.path || '';
|
|
2172
3399
|
this.agentsExists = !!res.exists;
|
|
2173
3400
|
this.agentsLineEnding = res.lineEnding === '\r\n' ? '\r\n' : '\n';
|
|
3401
|
+
this.resetAgentsDiffState();
|
|
2174
3402
|
this.showAgentsModal = true;
|
|
2175
3403
|
} catch (e) {
|
|
2176
3404
|
this.showMessage('加载文件失败', 'error');
|
|
@@ -2197,9 +3425,11 @@ import { createSkillsMethods } from './modules/skills.methods.mjs';
|
|
|
2197
3425
|
this.showMessage(`OpenClaw 配置解析失败,已使用默认 Workspace:${res.configError}`, 'error');
|
|
2198
3426
|
}
|
|
2199
3427
|
this.agentsContent = res.content || '';
|
|
3428
|
+
this.agentsOriginalContent = this.agentsContent;
|
|
2200
3429
|
this.agentsPath = res.path || '';
|
|
2201
3430
|
this.agentsExists = !!res.exists;
|
|
2202
3431
|
this.agentsLineEnding = res.lineEnding === '\r\n' ? '\r\n' : '\n';
|
|
3432
|
+
this.resetAgentsDiffState();
|
|
2203
3433
|
this.showAgentsModal = true;
|
|
2204
3434
|
} catch (e) {
|
|
2205
3435
|
this.showMessage('加载文件失败', 'error');
|
|
@@ -2228,18 +3458,287 @@ import { createSkillsMethods } from './modules/skills.methods.mjs';
|
|
|
2228
3458
|
this.agentsWorkspaceFileName = '';
|
|
2229
3459
|
},
|
|
2230
3460
|
|
|
2231
|
-
|
|
3461
|
+
resetAgentsDiffState() {
|
|
3462
|
+
this.agentsDiffVisible = false;
|
|
3463
|
+
this.agentsDiffLoading = false;
|
|
3464
|
+
this.agentsDiffError = '';
|
|
3465
|
+
this.agentsDiffLines = [];
|
|
3466
|
+
this.agentsDiffStats = {
|
|
3467
|
+
added: 0,
|
|
3468
|
+
removed: 0,
|
|
3469
|
+
unchanged: 0
|
|
3470
|
+
};
|
|
3471
|
+
this.agentsDiffTruncated = false;
|
|
3472
|
+
this.agentsDiffHasChangesValue = false;
|
|
3473
|
+
this.agentsDiffFingerprint = '';
|
|
3474
|
+
this._agentsDiffPreviewRequestToken = null;
|
|
3475
|
+
},
|
|
3476
|
+
handleGlobalKeydown(event) {
|
|
3477
|
+
if (!event || event.key !== 'Escape') {
|
|
3478
|
+
return;
|
|
3479
|
+
}
|
|
3480
|
+
if (this.showConfirmDialog) {
|
|
3481
|
+
event.preventDefault();
|
|
3482
|
+
event.stopPropagation();
|
|
3483
|
+
this.resolveConfirmDialog(false);
|
|
3484
|
+
return;
|
|
3485
|
+
}
|
|
3486
|
+
if (!this.showAgentsModal) {
|
|
3487
|
+
return;
|
|
3488
|
+
}
|
|
3489
|
+
event.preventDefault();
|
|
3490
|
+
event.stopPropagation();
|
|
3491
|
+
if (this.agentsSaving || this.agentsDiffLoading) {
|
|
3492
|
+
return;
|
|
3493
|
+
}
|
|
3494
|
+
if (this.agentsDiffVisible) {
|
|
3495
|
+
this.resetAgentsDiffState();
|
|
3496
|
+
return;
|
|
3497
|
+
}
|
|
3498
|
+
this.closeAgentsModal();
|
|
3499
|
+
},
|
|
3500
|
+
hasPendingAgentsDraft() {
|
|
3501
|
+
if (!this.showAgentsModal || this.agentsLoading || this.agentsSaving) {
|
|
3502
|
+
return false;
|
|
3503
|
+
}
|
|
3504
|
+
return this.hasAgentsContentChanged() || this.agentsDiffVisible;
|
|
3505
|
+
},
|
|
3506
|
+
handleBeforeUnload(event) {
|
|
3507
|
+
if (!this.hasPendingAgentsDraft()) {
|
|
3508
|
+
return;
|
|
3509
|
+
}
|
|
3510
|
+
if (event && typeof event.preventDefault === 'function') {
|
|
3511
|
+
event.preventDefault();
|
|
3512
|
+
event.returnValue = '';
|
|
3513
|
+
}
|
|
3514
|
+
return '';
|
|
3515
|
+
},
|
|
3516
|
+
hasAgentsContentChanged() {
|
|
3517
|
+
const original = typeof this.agentsOriginalContent === 'string' ? this.agentsOriginalContent : '';
|
|
3518
|
+
const current = typeof this.agentsContent === 'string' ? this.agentsContent : '';
|
|
3519
|
+
return original !== current;
|
|
3520
|
+
},
|
|
3521
|
+
requestConfirmDialog(options = {}) {
|
|
3522
|
+
if (typeof this.confirmDialogResolver === 'function') {
|
|
3523
|
+
this.confirmDialogResolver(false);
|
|
3524
|
+
}
|
|
3525
|
+
const confirmDisabled = options.confirmDisabled;
|
|
3526
|
+
this.confirmDialogTitle = typeof options.title === 'string' && options.title.trim()
|
|
3527
|
+
? options.title.trim()
|
|
3528
|
+
: '请确认操作';
|
|
3529
|
+
this.confirmDialogMessage = typeof options.message === 'string' ? options.message : '';
|
|
3530
|
+
this.confirmDialogConfirmText = typeof options.confirmText === 'string' && options.confirmText.trim()
|
|
3531
|
+
? options.confirmText.trim()
|
|
3532
|
+
: '确认';
|
|
3533
|
+
this.confirmDialogCancelText = typeof options.cancelText === 'string' && options.cancelText.trim()
|
|
3534
|
+
? options.cancelText.trim()
|
|
3535
|
+
: '取消';
|
|
3536
|
+
this.confirmDialogDanger = !!options.danger;
|
|
3537
|
+
this.confirmDialogConfirmDisabled = typeof confirmDisabled === 'function' ? false : !!confirmDisabled;
|
|
3538
|
+
this.confirmDialogDisableWhen = typeof confirmDisabled === 'function' ? confirmDisabled : null;
|
|
3539
|
+
this.showConfirmDialog = true;
|
|
3540
|
+
return new Promise((resolve) => {
|
|
3541
|
+
this.confirmDialogResolver = resolve;
|
|
3542
|
+
});
|
|
3543
|
+
},
|
|
3544
|
+
isConfirmDialogDisabled() {
|
|
3545
|
+
if (typeof this.confirmDialogDisableWhen === 'function') {
|
|
3546
|
+
try {
|
|
3547
|
+
return !!this.confirmDialogDisableWhen.call(this);
|
|
3548
|
+
} catch (_) {
|
|
3549
|
+
return true;
|
|
3550
|
+
}
|
|
3551
|
+
}
|
|
3552
|
+
return !!this.confirmDialogConfirmDisabled;
|
|
3553
|
+
},
|
|
3554
|
+
resolveConfirmDialog(confirmed) {
|
|
3555
|
+
const resolver = typeof this.confirmDialogResolver === 'function'
|
|
3556
|
+
? this.confirmDialogResolver
|
|
3557
|
+
: null;
|
|
3558
|
+
this.showConfirmDialog = false;
|
|
3559
|
+
this.confirmDialogTitle = '';
|
|
3560
|
+
this.confirmDialogMessage = '';
|
|
3561
|
+
this.confirmDialogConfirmText = '确认';
|
|
3562
|
+
this.confirmDialogCancelText = '取消';
|
|
3563
|
+
this.confirmDialogDanger = false;
|
|
3564
|
+
this.confirmDialogConfirmDisabled = false;
|
|
3565
|
+
this.confirmDialogDisableWhen = null;
|
|
3566
|
+
this.confirmDialogResolver = null;
|
|
3567
|
+
if (resolver) {
|
|
3568
|
+
resolver(!!confirmed);
|
|
3569
|
+
}
|
|
3570
|
+
},
|
|
3571
|
+
closeConfirmDialog() {
|
|
3572
|
+
this.resolveConfirmDialog(false);
|
|
3573
|
+
},
|
|
3574
|
+
onAgentsContentInput() {
|
|
3575
|
+
if (this.agentsDiffVisible || this.agentsDiffLines.length) {
|
|
3576
|
+
this.resetAgentsDiffState();
|
|
3577
|
+
}
|
|
3578
|
+
},
|
|
3579
|
+
buildAgentsDiffFingerprint() {
|
|
3580
|
+
const context = this.agentsContext || 'codex';
|
|
3581
|
+
const fileName = context === 'openclaw-workspace'
|
|
3582
|
+
? (this.agentsWorkspaceFileName || '')
|
|
3583
|
+
: '';
|
|
3584
|
+
const lineEnding = this.agentsLineEnding || '\n';
|
|
3585
|
+
const content = typeof this.agentsContent === 'string' ? this.agentsContent : '';
|
|
3586
|
+
const original = typeof this.agentsOriginalContent === 'string' ? this.agentsOriginalContent : '';
|
|
3587
|
+
return `${context}::${fileName}::${lineEnding}::${content.length}::${content}::${original.length}::${original}`;
|
|
3588
|
+
},
|
|
3589
|
+
async prepareAgentsDiff() {
|
|
3590
|
+
const requestFingerprint = this.buildAgentsDiffFingerprint();
|
|
3591
|
+
const requestToken = Symbol('agents-diff-preview');
|
|
3592
|
+
this._agentsDiffPreviewRequestToken = requestToken;
|
|
3593
|
+
this.agentsDiffVisible = true;
|
|
3594
|
+
this.agentsDiffLoading = true;
|
|
3595
|
+
this.agentsDiffError = '';
|
|
3596
|
+
this.agentsDiffLines = [];
|
|
3597
|
+
this.agentsDiffStats = {
|
|
3598
|
+
added: 0,
|
|
3599
|
+
removed: 0,
|
|
3600
|
+
unchanged: 0
|
|
3601
|
+
};
|
|
3602
|
+
this.agentsDiffTruncated = false;
|
|
3603
|
+
this.agentsDiffHasChangesValue = false;
|
|
3604
|
+
try {
|
|
3605
|
+
const shouldApplyPreviewState = () => shouldApplyAgentsDiffPreviewResponse({
|
|
3606
|
+
isVisible: this.agentsDiffVisible,
|
|
3607
|
+
requestToken,
|
|
3608
|
+
activeRequestToken: this._agentsDiffPreviewRequestToken,
|
|
3609
|
+
requestFingerprint,
|
|
3610
|
+
currentFingerprint: this.buildAgentsDiffFingerprint()
|
|
3611
|
+
});
|
|
3612
|
+
const applyPreviewState = (diff) => {
|
|
3613
|
+
if (!shouldApplyPreviewState()) {
|
|
3614
|
+
return false;
|
|
3615
|
+
}
|
|
3616
|
+
const normalizedDiff = diff && typeof diff === 'object' ? diff : {};
|
|
3617
|
+
const rawLines = Array.isArray(normalizedDiff.lines) ? normalizedDiff.lines : [];
|
|
3618
|
+
this.agentsDiffLines = rawLines.filter(line => line && line.type);
|
|
3619
|
+
this.agentsDiffTruncated = !!normalizedDiff.truncated;
|
|
3620
|
+
this.agentsDiffHasChangesValue = !!normalizedDiff.hasChanges;
|
|
3621
|
+
if (normalizedDiff.stats && typeof normalizedDiff.stats === 'object') {
|
|
3622
|
+
this.agentsDiffStats = {
|
|
3623
|
+
added: Number(normalizedDiff.stats.added || 0),
|
|
3624
|
+
removed: Number(normalizedDiff.stats.removed || 0),
|
|
3625
|
+
unchanged: Number(normalizedDiff.stats.unchanged || 0)
|
|
3626
|
+
};
|
|
3627
|
+
} else {
|
|
3628
|
+
const stats = { added: 0, removed: 0, unchanged: 0 };
|
|
3629
|
+
for (const line of this.agentsDiffLines) {
|
|
3630
|
+
if (line && line.type === 'add') stats.added += 1;
|
|
3631
|
+
else if (line && line.type === 'del') stats.removed += 1;
|
|
3632
|
+
else stats.unchanged += 1;
|
|
3633
|
+
}
|
|
3634
|
+
this.agentsDiffStats = stats;
|
|
3635
|
+
}
|
|
3636
|
+
this.agentsDiffFingerprint = requestFingerprint;
|
|
3637
|
+
return true;
|
|
3638
|
+
};
|
|
3639
|
+
const previewRequest = buildAgentsDiffPreviewRequest({
|
|
3640
|
+
baseContent: this.agentsOriginalContent,
|
|
3641
|
+
content: this.agentsContent,
|
|
3642
|
+
lineEnding: this.agentsLineEnding,
|
|
3643
|
+
context: this.agentsContext,
|
|
3644
|
+
fileName: this.agentsWorkspaceFileName
|
|
3645
|
+
});
|
|
3646
|
+
if (previewRequest.exceedsBodyLimit) {
|
|
3647
|
+
applyPreviewState(buildAgentsDiffPreview({
|
|
3648
|
+
baseContent: this.agentsOriginalContent,
|
|
3649
|
+
content: this.agentsContent
|
|
3650
|
+
}));
|
|
3651
|
+
return;
|
|
3652
|
+
}
|
|
3653
|
+
const res = await apiWithMeta('preview-agents-diff', previewRequest.params);
|
|
3654
|
+
if (!shouldApplyPreviewState()) {
|
|
3655
|
+
return;
|
|
3656
|
+
}
|
|
3657
|
+
if (res.error) {
|
|
3658
|
+
if (isAgentsDiffPreviewPayloadTooLarge(res)) {
|
|
3659
|
+
applyPreviewState(buildAgentsDiffPreview({
|
|
3660
|
+
baseContent: this.agentsOriginalContent,
|
|
3661
|
+
content: this.agentsContent
|
|
3662
|
+
}));
|
|
3663
|
+
return;
|
|
3664
|
+
}
|
|
3665
|
+
this.agentsDiffError = res.error;
|
|
3666
|
+
return;
|
|
3667
|
+
}
|
|
3668
|
+
applyPreviewState(res.diff);
|
|
3669
|
+
} catch (e) {
|
|
3670
|
+
if (shouldApplyAgentsDiffPreviewResponse({
|
|
3671
|
+
isVisible: this.agentsDiffVisible,
|
|
3672
|
+
requestToken,
|
|
3673
|
+
activeRequestToken: this._agentsDiffPreviewRequestToken,
|
|
3674
|
+
requestFingerprint,
|
|
3675
|
+
currentFingerprint: this.buildAgentsDiffFingerprint()
|
|
3676
|
+
})) {
|
|
3677
|
+
this.agentsDiffError = '生成差异失败';
|
|
3678
|
+
}
|
|
3679
|
+
} finally {
|
|
3680
|
+
if (this._agentsDiffPreviewRequestToken === requestToken) {
|
|
3681
|
+
this.agentsDiffLoading = false;
|
|
3682
|
+
}
|
|
3683
|
+
}
|
|
3684
|
+
},
|
|
3685
|
+
|
|
3686
|
+
async closeAgentsModal(options = {}) {
|
|
3687
|
+
const force = !!(options && options.force);
|
|
3688
|
+
const shouldConfirmClose = !force
|
|
3689
|
+
&& this.hasPendingAgentsDraft();
|
|
3690
|
+
if (shouldConfirmClose) {
|
|
3691
|
+
const message = this.agentsDiffVisible
|
|
3692
|
+
? '当前处于差异预览模式,改动尚未保存。确认放弃改动并关闭吗?'
|
|
3693
|
+
: '存在未保存改动,确认放弃改动并关闭吗?(关闭页面或应用也会丢失改动)';
|
|
3694
|
+
const confirmed = await this.requestConfirmDialog({
|
|
3695
|
+
title: '放弃未保存改动',
|
|
3696
|
+
message,
|
|
3697
|
+
confirmText: '放弃并关闭',
|
|
3698
|
+
cancelText: '继续编辑',
|
|
3699
|
+
danger: true
|
|
3700
|
+
});
|
|
3701
|
+
if (!confirmed) {
|
|
3702
|
+
return;
|
|
3703
|
+
}
|
|
3704
|
+
}
|
|
2232
3705
|
this.showAgentsModal = false;
|
|
2233
3706
|
this.agentsContent = '';
|
|
3707
|
+
this.agentsOriginalContent = '';
|
|
2234
3708
|
this.agentsPath = '';
|
|
2235
3709
|
this.agentsExists = false;
|
|
2236
3710
|
this.agentsLineEnding = '\n';
|
|
2237
3711
|
this.agentsSaving = false;
|
|
2238
3712
|
this.agentsWorkspaceFileName = '';
|
|
3713
|
+
this.resetAgentsDiffState();
|
|
2239
3714
|
this.setAgentsModalContext('codex');
|
|
2240
3715
|
},
|
|
2241
3716
|
|
|
2242
3717
|
async applyAgentsContent() {
|
|
3718
|
+
if (!this.agentsDiffVisible) {
|
|
3719
|
+
if (!this.hasAgentsContentChanged()) {
|
|
3720
|
+
this.showMessage('未检测到改动', 'info');
|
|
3721
|
+
return;
|
|
3722
|
+
}
|
|
3723
|
+
await this.prepareAgentsDiff();
|
|
3724
|
+
return;
|
|
3725
|
+
}
|
|
3726
|
+
if (this.agentsDiffLoading) {
|
|
3727
|
+
return;
|
|
3728
|
+
}
|
|
3729
|
+
if (this.agentsDiffError) {
|
|
3730
|
+
this.showMessage(this.agentsDiffError, 'error');
|
|
3731
|
+
return;
|
|
3732
|
+
}
|
|
3733
|
+
const fingerprint = this.buildAgentsDiffFingerprint();
|
|
3734
|
+
if (this.agentsDiffFingerprint !== fingerprint) {
|
|
3735
|
+
await this.prepareAgentsDiff();
|
|
3736
|
+
return;
|
|
3737
|
+
}
|
|
3738
|
+
if (!this.agentsDiffHasChanges) {
|
|
3739
|
+
this.showMessage('未检测到改动', 'info');
|
|
3740
|
+
return;
|
|
3741
|
+
}
|
|
2243
3742
|
this.agentsSaving = true;
|
|
2244
3743
|
try {
|
|
2245
3744
|
let action = 'apply-agents-file';
|
|
@@ -2262,7 +3761,7 @@ import { createSkillsMethods } from './modules/skills.methods.mjs';
|
|
|
2262
3761
|
? `工作区文件已保存${this.agentsWorkspaceFileName ? `: ${this.agentsWorkspaceFileName}` : ''}`
|
|
2263
3762
|
: (this.agentsContext === 'openclaw' ? 'OpenClaw AGENTS.md 已保存' : 'AGENTS.md 已保存');
|
|
2264
3763
|
this.showMessage(successLabel, 'success');
|
|
2265
|
-
this.closeAgentsModal();
|
|
3764
|
+
this.closeAgentsModal({ force: true });
|
|
2266
3765
|
} catch (e) {
|
|
2267
3766
|
this.showMessage('保存失败', 'error');
|
|
2268
3767
|
} finally {
|
|
@@ -2315,7 +3814,7 @@ import { createSkillsMethods } from './modules/skills.methods.mjs';
|
|
|
2315
3814
|
? String(providerOrName.name || '')
|
|
2316
3815
|
: String(providerOrName);
|
|
2317
3816
|
const normalized = rawName.trim().toLowerCase();
|
|
2318
|
-
return normalized === 'local'
|
|
3817
|
+
return normalized === 'local';
|
|
2319
3818
|
},
|
|
2320
3819
|
|
|
2321
3820
|
providerPillState(provider) {
|
|
@@ -2355,7 +3854,7 @@ import { createSkillsMethods } from './modules/skills.methods.mjs';
|
|
|
2355
3854
|
if (!providerOrName) return false;
|
|
2356
3855
|
if (typeof providerOrName === 'object') {
|
|
2357
3856
|
const directName = String(providerOrName.name || '').trim().toLowerCase();
|
|
2358
|
-
if (directName === 'local'
|
|
3857
|
+
if (directName === 'local') {
|
|
2359
3858
|
return true;
|
|
2360
3859
|
}
|
|
2361
3860
|
return !!providerOrName.nonDeletable;
|
|
@@ -2363,7 +3862,7 @@ import { createSkillsMethods } from './modules/skills.methods.mjs';
|
|
|
2363
3862
|
const name = String(providerOrName).trim();
|
|
2364
3863
|
if (!name) return false;
|
|
2365
3864
|
const normalized = name.toLowerCase();
|
|
2366
|
-
if (normalized === 'local'
|
|
3865
|
+
if (normalized === 'local') {
|
|
2367
3866
|
return true;
|
|
2368
3867
|
}
|
|
2369
3868
|
const target = (this.providersList || []).find((item) => item && item.name === name);
|
|
@@ -2622,12 +4121,18 @@ import { createSkillsMethods } from './modules/skills.methods.mjs';
|
|
|
2622
4121
|
this.refreshClaudeModelContext();
|
|
2623
4122
|
},
|
|
2624
4123
|
|
|
2625
|
-
deleteClaudeConfig(name) {
|
|
4124
|
+
async deleteClaudeConfig(name) {
|
|
2626
4125
|
if (Object.keys(this.claudeConfigs).length <= 1) {
|
|
2627
4126
|
return this.showMessage('至少保留一项', 'error');
|
|
2628
4127
|
}
|
|
2629
|
-
|
|
2630
|
-
|
|
4128
|
+
const confirmed = await this.requestConfirmDialog({
|
|
4129
|
+
title: '删除 Claude 配置',
|
|
4130
|
+
message: `确定删除配置 "${name}"?`,
|
|
4131
|
+
confirmText: '删除',
|
|
4132
|
+
cancelText: '取消',
|
|
4133
|
+
danger: true
|
|
4134
|
+
});
|
|
4135
|
+
if (!confirmed) return;
|
|
2631
4136
|
|
|
2632
4137
|
delete this.claudeConfigs[name];
|
|
2633
4138
|
if (this.currentClaudeConfig === name) {
|
|
@@ -3713,11 +5218,18 @@ import { createSkillsMethods } from './modules/skills.methods.mjs';
|
|
|
3713
5218
|
}
|
|
3714
5219
|
},
|
|
3715
5220
|
|
|
3716
|
-
deleteOpenclawConfig(name) {
|
|
5221
|
+
async deleteOpenclawConfig(name) {
|
|
3717
5222
|
if (Object.keys(this.openclawConfigs).length <= 1) {
|
|
3718
5223
|
return this.showMessage('至少保留一项', 'error');
|
|
3719
5224
|
}
|
|
3720
|
-
|
|
5225
|
+
const confirmed = await this.requestConfirmDialog({
|
|
5226
|
+
title: '删除 OpenClaw 配置',
|
|
5227
|
+
message: `确定删除配置 "${name}"?`,
|
|
5228
|
+
confirmText: '删除',
|
|
5229
|
+
cancelText: '取消',
|
|
5230
|
+
danger: true
|
|
5231
|
+
});
|
|
5232
|
+
if (!confirmed) return;
|
|
3721
5233
|
delete this.openclawConfigs[name];
|
|
3722
5234
|
if (this.currentOpenclawConfig === name) {
|
|
3723
5235
|
this.currentOpenclawConfig = Object.keys(this.openclawConfigs)[0];
|
|
@@ -4022,215 +5534,6 @@ import { createSkillsMethods } from './modules/skills.methods.mjs';
|
|
|
4022
5534
|
}
|
|
4023
5535
|
},
|
|
4024
5536
|
|
|
4025
|
-
triggerCodexAuthUpload() {
|
|
4026
|
-
const input = this.$refs.codexAuthImportInput;
|
|
4027
|
-
if (input) {
|
|
4028
|
-
input.value = '';
|
|
4029
|
-
input.click();
|
|
4030
|
-
}
|
|
4031
|
-
},
|
|
4032
|
-
|
|
4033
|
-
handleCodexAuthImportChange(event) {
|
|
4034
|
-
const file = event && event.target && event.target.files ? event.target.files[0] : null;
|
|
4035
|
-
if (file) {
|
|
4036
|
-
void this.importCodexAuthFile(file);
|
|
4037
|
-
}
|
|
4038
|
-
},
|
|
4039
|
-
|
|
4040
|
-
resetCodexAuthImportInput() {
|
|
4041
|
-
const el = this.$refs.codexAuthImportInput;
|
|
4042
|
-
if (el) {
|
|
4043
|
-
el.value = '';
|
|
4044
|
-
}
|
|
4045
|
-
},
|
|
4046
|
-
|
|
4047
|
-
async importCodexAuthFile(file) {
|
|
4048
|
-
this.codexAuthImportLoading = true;
|
|
4049
|
-
try {
|
|
4050
|
-
const base64 = await this.readFileAsBase64(file);
|
|
4051
|
-
const res = await api('import-auth-profile', {
|
|
4052
|
-
fileName: file.name || 'codex-auth.json',
|
|
4053
|
-
fileBase64: base64,
|
|
4054
|
-
activate: true
|
|
4055
|
-
});
|
|
4056
|
-
if (res && res.error) {
|
|
4057
|
-
this.showMessage(res.error, 'error');
|
|
4058
|
-
return;
|
|
4059
|
-
}
|
|
4060
|
-
await this.loadCodexAuthProfiles({ silent: true });
|
|
4061
|
-
this.showMessage('认证文件已导入并切换', 'success');
|
|
4062
|
-
} catch (e) {
|
|
4063
|
-
this.showMessage('导入认证文件失败', 'error');
|
|
4064
|
-
} finally {
|
|
4065
|
-
this.codexAuthImportLoading = false;
|
|
4066
|
-
this.resetCodexAuthImportInput();
|
|
4067
|
-
}
|
|
4068
|
-
},
|
|
4069
|
-
|
|
4070
|
-
async switchCodexAuthProfile(name) {
|
|
4071
|
-
const key = String(name || '').trim();
|
|
4072
|
-
if (!key || this.codexAuthSwitching[key]) return;
|
|
4073
|
-
this.codexAuthSwitching[key] = true;
|
|
4074
|
-
try {
|
|
4075
|
-
const res = await api('switch-auth-profile', { name: key });
|
|
4076
|
-
if (res && res.error) {
|
|
4077
|
-
this.showMessage(res.error, 'error');
|
|
4078
|
-
return;
|
|
4079
|
-
}
|
|
4080
|
-
await this.loadCodexAuthProfiles({ silent: true });
|
|
4081
|
-
this.showMessage(`已切换认证: ${key}`, 'success');
|
|
4082
|
-
} catch (e) {
|
|
4083
|
-
this.showMessage('切换认证失败', 'error');
|
|
4084
|
-
} finally {
|
|
4085
|
-
this.codexAuthSwitching[key] = false;
|
|
4086
|
-
}
|
|
4087
|
-
},
|
|
4088
|
-
|
|
4089
|
-
async deleteCodexAuthProfile(name) {
|
|
4090
|
-
const key = String(name || '').trim();
|
|
4091
|
-
if (!key || this.codexAuthDeleting[key]) return;
|
|
4092
|
-
this.codexAuthDeleting[key] = true;
|
|
4093
|
-
try {
|
|
4094
|
-
const res = await api('delete-auth-profile', { name: key });
|
|
4095
|
-
if (res && res.error) {
|
|
4096
|
-
this.showMessage(res.error, 'error');
|
|
4097
|
-
return;
|
|
4098
|
-
}
|
|
4099
|
-
await this.loadCodexAuthProfiles({ silent: true });
|
|
4100
|
-
const switchedTip = res && res.switchedTo ? `,已切换到 ${res.switchedTo}` : '';
|
|
4101
|
-
this.showMessage(`已删除认证${switchedTip}`, 'success');
|
|
4102
|
-
} catch (e) {
|
|
4103
|
-
this.showMessage('删除认证失败', 'error');
|
|
4104
|
-
} finally {
|
|
4105
|
-
this.codexAuthDeleting[key] = false;
|
|
4106
|
-
}
|
|
4107
|
-
},
|
|
4108
|
-
|
|
4109
|
-
mergeProxySettings(nextSettings) {
|
|
4110
|
-
const safe = nextSettings && typeof nextSettings === 'object' ? nextSettings : {};
|
|
4111
|
-
const port = parseInt(String(safe.port), 10);
|
|
4112
|
-
const timeoutMs = parseInt(String(safe.timeoutMs), 10);
|
|
4113
|
-
this.proxySettings = {
|
|
4114
|
-
enabled: safe.enabled !== false,
|
|
4115
|
-
host: typeof safe.host === 'string' && safe.host.trim() ? safe.host.trim() : '127.0.0.1',
|
|
4116
|
-
port: Number.isFinite(port) ? port : 8318,
|
|
4117
|
-
provider: typeof safe.provider === 'string' ? safe.provider.trim() : '',
|
|
4118
|
-
authSource: safe.authSource === 'profile' || safe.authSource === 'none' ? safe.authSource : 'provider',
|
|
4119
|
-
timeoutMs: Number.isFinite(timeoutMs) ? timeoutMs : 30000
|
|
4120
|
-
};
|
|
4121
|
-
},
|
|
4122
|
-
|
|
4123
|
-
async loadProxyStatus(options = {}) {
|
|
4124
|
-
const silent = !!options.silent;
|
|
4125
|
-
this.proxyLoading = true;
|
|
4126
|
-
try {
|
|
4127
|
-
const res = await api('proxy-status');
|
|
4128
|
-
if (res && res.error) {
|
|
4129
|
-
if (!silent) {
|
|
4130
|
-
this.showMessage(res.error, 'error');
|
|
4131
|
-
}
|
|
4132
|
-
return;
|
|
4133
|
-
}
|
|
4134
|
-
this.mergeProxySettings(res && res.settings ? res.settings : {});
|
|
4135
|
-
this.proxyRuntime = res && res.runtime ? { running: true, ...res.runtime } : null;
|
|
4136
|
-
} catch (e) {
|
|
4137
|
-
if (!silent) {
|
|
4138
|
-
this.showMessage('读取代理状态失败', 'error');
|
|
4139
|
-
}
|
|
4140
|
-
} finally {
|
|
4141
|
-
this.proxyLoading = false;
|
|
4142
|
-
}
|
|
4143
|
-
},
|
|
4144
|
-
|
|
4145
|
-
async saveProxySettings(options = {}) {
|
|
4146
|
-
const silent = !!options.silent;
|
|
4147
|
-
this.proxySaving = true;
|
|
4148
|
-
try {
|
|
4149
|
-
const res = await api('proxy-save-config', this.proxySettings);
|
|
4150
|
-
if (res && res.error) {
|
|
4151
|
-
if (!silent) {
|
|
4152
|
-
this.showMessage(res.error, 'error');
|
|
4153
|
-
}
|
|
4154
|
-
return;
|
|
4155
|
-
}
|
|
4156
|
-
if (res && res.settings) {
|
|
4157
|
-
this.mergeProxySettings(res.settings);
|
|
4158
|
-
}
|
|
4159
|
-
if (!silent) {
|
|
4160
|
-
this.showMessage('代理配置已保存', 'success');
|
|
4161
|
-
}
|
|
4162
|
-
} catch (e) {
|
|
4163
|
-
if (!silent) {
|
|
4164
|
-
this.showMessage('保存代理配置失败', 'error');
|
|
4165
|
-
}
|
|
4166
|
-
} finally {
|
|
4167
|
-
this.proxySaving = false;
|
|
4168
|
-
}
|
|
4169
|
-
},
|
|
4170
|
-
|
|
4171
|
-
async startBuiltinProxy() {
|
|
4172
|
-
this.proxyStarting = true;
|
|
4173
|
-
try {
|
|
4174
|
-
const res = await api('proxy-start', {
|
|
4175
|
-
...this.proxySettings,
|
|
4176
|
-
enabled: true
|
|
4177
|
-
});
|
|
4178
|
-
if (res && res.error) {
|
|
4179
|
-
this.showMessage(res.error, 'error');
|
|
4180
|
-
return;
|
|
4181
|
-
}
|
|
4182
|
-
if (res && res.settings) {
|
|
4183
|
-
this.mergeProxySettings(res.settings);
|
|
4184
|
-
}
|
|
4185
|
-
await this.loadProxyStatus({ silent: true });
|
|
4186
|
-
const listenTip = res && res.listenUrl ? `:${res.listenUrl}` : '';
|
|
4187
|
-
this.showMessage(`代理已启动${listenTip}`, 'success');
|
|
4188
|
-
} catch (e) {
|
|
4189
|
-
this.showMessage('启动代理失败', 'error');
|
|
4190
|
-
} finally {
|
|
4191
|
-
this.proxyStarting = false;
|
|
4192
|
-
}
|
|
4193
|
-
},
|
|
4194
|
-
|
|
4195
|
-
async stopBuiltinProxy() {
|
|
4196
|
-
this.proxyStopping = true;
|
|
4197
|
-
try {
|
|
4198
|
-
const res = await api('proxy-stop');
|
|
4199
|
-
if (res && res.error) {
|
|
4200
|
-
this.showMessage(res.error, 'error');
|
|
4201
|
-
return;
|
|
4202
|
-
}
|
|
4203
|
-
await this.loadProxyStatus({ silent: true });
|
|
4204
|
-
this.showMessage('代理已停止', 'success');
|
|
4205
|
-
} catch (e) {
|
|
4206
|
-
this.showMessage('停止代理失败', 'error');
|
|
4207
|
-
} finally {
|
|
4208
|
-
this.proxyStopping = false;
|
|
4209
|
-
}
|
|
4210
|
-
},
|
|
4211
|
-
|
|
4212
|
-
async applyBuiltinProxyProvider() {
|
|
4213
|
-
this.proxyApplying = true;
|
|
4214
|
-
try {
|
|
4215
|
-
const saveRes = await api('proxy-save-config', this.proxySettings);
|
|
4216
|
-
if (saveRes && saveRes.error) {
|
|
4217
|
-
this.showMessage(saveRes.error, 'error');
|
|
4218
|
-
return;
|
|
4219
|
-
}
|
|
4220
|
-
const res = await api('proxy-apply-provider', { switchToProxy: true });
|
|
4221
|
-
if (res && res.error) {
|
|
4222
|
-
this.showMessage(res.error, 'error');
|
|
4223
|
-
return;
|
|
4224
|
-
}
|
|
4225
|
-
await this.loadAll();
|
|
4226
|
-
this.showMessage('本地代理 provider 已写入并切换', 'success');
|
|
4227
|
-
} catch (e) {
|
|
4228
|
-
this.showMessage('应用代理 provider 失败', 'error');
|
|
4229
|
-
} finally {
|
|
4230
|
-
this.proxyApplying = false;
|
|
4231
|
-
}
|
|
4232
|
-
},
|
|
4233
|
-
|
|
4234
5537
|
showMessage(text, type) {
|
|
4235
5538
|
this.message = text;
|
|
4236
5539
|
this.messageType = type || 'info';
|
|
@@ -4243,4 +5546,3 @@ import { createSkillsMethods } from './modules/skills.methods.mjs';
|
|
|
4243
5546
|
|
|
4244
5547
|
app.mount('#app');
|
|
4245
5548
|
});
|
|
4246
|
-
|