codexmate 0.0.16 → 0.0.18
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.en.md +42 -24
- package/README.md +42 -24
- package/cli.js +1457 -157
- package/lib/text-diff.js +303 -0
- package/package.json +2 -2
- package/web-ui/app.js +1946 -247
- package/web-ui/index.html +321 -78
- package/web-ui/logic.mjs +503 -13
- package/web-ui/modules/skills.methods.mjs +7 -1
- package/web-ui/session-helpers.mjs +350 -0
- package/web-ui/styles.css +679 -31
package/web-ui/app.js
CHANGED
|
@@ -4,16 +4,27 @@
|
|
|
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,
|
|
14
17
|
buildSessionTimelineNodes,
|
|
15
|
-
normalizeSessionMessageRole
|
|
18
|
+
normalizeSessionMessageRole,
|
|
19
|
+
runLatestOnlyQueue,
|
|
20
|
+
shouldForceCompactLayoutMode
|
|
16
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';
|
|
17
28
|
import {
|
|
18
29
|
CONFIG_MODE_SET,
|
|
19
30
|
getProviderConfigModeMeta,
|
|
@@ -41,6 +52,8 @@ import { createSkillsMethods } from './modules/skills.methods.mjs';
|
|
|
41
52
|
const API_BASE = (location && location.origin && location.origin !== 'null')
|
|
42
53
|
? location.origin
|
|
43
54
|
: 'http://localhost:3737';
|
|
55
|
+
const SESSION_TRASH_LIST_LIMIT = 500;
|
|
56
|
+
const SESSION_TRASH_PAGE_SIZE = 200;
|
|
44
57
|
const DEFAULT_OPENCLAW_TEMPLATE = `{
|
|
45
58
|
// OpenClaw config (JSON5)
|
|
46
59
|
agent: {
|
|
@@ -53,15 +66,45 @@ import { createSkillsMethods } from './modules/skills.methods.mjs';
|
|
|
53
66
|
}
|
|
54
67
|
}`;
|
|
55
68
|
|
|
56
|
-
async function
|
|
57
|
-
|
|
69
|
+
async function postApi(action, params = {}) {
|
|
70
|
+
return await fetch(`${API_BASE}/api`, {
|
|
58
71
|
method: 'POST',
|
|
59
72
|
headers: { 'Content-Type': 'application/json' },
|
|
60
73
|
body: JSON.stringify({ action, params })
|
|
61
74
|
});
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
async function api(action, params = {}) {
|
|
78
|
+
const res = await postApi(action, params);
|
|
62
79
|
return await res.json();
|
|
63
80
|
}
|
|
64
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
|
+
|
|
65
108
|
const app = createApp({
|
|
66
109
|
data() {
|
|
67
110
|
return {
|
|
@@ -95,6 +138,13 @@ import { createSkillsMethods } from './modules/skills.methods.mjs';
|
|
|
95
138
|
showAgentsModal: false,
|
|
96
139
|
showSkillsModal: false,
|
|
97
140
|
showInstallModal: false,
|
|
141
|
+
showConfirmDialog: false,
|
|
142
|
+
confirmDialogTitle: '',
|
|
143
|
+
confirmDialogMessage: '',
|
|
144
|
+
confirmDialogConfirmText: '确认',
|
|
145
|
+
confirmDialogCancelText: '取消',
|
|
146
|
+
confirmDialogDanger: false,
|
|
147
|
+
confirmDialogResolver: null,
|
|
98
148
|
configTemplateContent: '',
|
|
99
149
|
configTemplateApplying: false,
|
|
100
150
|
codexApplying: false,
|
|
@@ -104,6 +154,19 @@ import { createSkillsMethods } from './modules/skills.methods.mjs';
|
|
|
104
154
|
agentsLineEnding: '\n',
|
|
105
155
|
agentsLoading: false,
|
|
106
156
|
agentsSaving: false,
|
|
157
|
+
agentsOriginalContent: '',
|
|
158
|
+
agentsDiffVisible: false,
|
|
159
|
+
agentsDiffLoading: false,
|
|
160
|
+
agentsDiffError: '',
|
|
161
|
+
agentsDiffLines: [],
|
|
162
|
+
agentsDiffStats: {
|
|
163
|
+
added: 0,
|
|
164
|
+
removed: 0,
|
|
165
|
+
unchanged: 0
|
|
166
|
+
},
|
|
167
|
+
agentsDiffTruncated: false,
|
|
168
|
+
agentsDiffHasChangesValue: false,
|
|
169
|
+
agentsDiffFingerprint: '',
|
|
107
170
|
agentsContext: 'codex',
|
|
108
171
|
agentsModalTitle: 'AGENTS.md 编辑器',
|
|
109
172
|
agentsModalHint: '保存后会写入目标 AGENTS.md(与 config.toml 同级)。',
|
|
@@ -120,7 +183,9 @@ import { createSkillsMethods } from './modules/skills.methods.mjs';
|
|
|
120
183
|
skillsImporting: false,
|
|
121
184
|
skillsZipImporting: false,
|
|
122
185
|
skillsExporting: false,
|
|
186
|
+
sessionPinnedMap: {},
|
|
123
187
|
sessionsList: [],
|
|
188
|
+
sessionsLoadedOnce: false,
|
|
124
189
|
sessionsLoading: false,
|
|
125
190
|
sessionFilterSource: 'all',
|
|
126
191
|
sessionPathFilter: '',
|
|
@@ -150,13 +215,31 @@ import { createSkillsMethods } from './modules/skills.methods.mjs';
|
|
|
150
215
|
activeSessionDetailClipped: false,
|
|
151
216
|
sessionDetailLoading: false,
|
|
152
217
|
sessionDetailRequestSeq: 0,
|
|
218
|
+
sessionDetailInitialMessageLimit: 80,
|
|
219
|
+
sessionDetailFetchStep: 80,
|
|
220
|
+
sessionDetailMessageLimit: 80,
|
|
221
|
+
sessionDetailMessageLimitCap: 1000,
|
|
153
222
|
sessionTimelineActiveKey: '',
|
|
154
223
|
sessionTimelineRafId: 0,
|
|
224
|
+
sessionTimelineLastSyncAt: 0,
|
|
225
|
+
sessionTimelineLastScrollTop: 0,
|
|
226
|
+
sessionTimelineLastAnchorY: 0,
|
|
227
|
+
sessionTimelineLastDirection: 0,
|
|
228
|
+
sessionTimelineEnabled: true,
|
|
155
229
|
sessionMessageRefMap: Object.create(null),
|
|
230
|
+
sessionMessageRefBinderMap: Object.create(null),
|
|
156
231
|
sessionPreviewScrollEl: null,
|
|
157
232
|
sessionPreviewContainerEl: null,
|
|
158
233
|
sessionPreviewHeaderEl: null,
|
|
159
234
|
sessionPreviewHeaderResizeObserver: null,
|
|
235
|
+
sessionListRenderEnabled: false,
|
|
236
|
+
sessionPreviewRenderEnabled: false,
|
|
237
|
+
sessionTabRenderTicket: 0,
|
|
238
|
+
sessionPreviewVisibleCount: 0,
|
|
239
|
+
sessionPreviewInitialBatchSize: 12,
|
|
240
|
+
sessionPreviewLoadStep: 24,
|
|
241
|
+
sessionPreviewPendingVisibleCount: 0,
|
|
242
|
+
sessionPreviewLoadingMore: false,
|
|
160
243
|
sessionStandalone: false,
|
|
161
244
|
sessionStandaloneError: '',
|
|
162
245
|
sessionStandaloneText: '',
|
|
@@ -170,6 +253,8 @@ import { createSkillsMethods } from './modules/skills.methods.mjs';
|
|
|
170
253
|
claudeSpeedLoading: {},
|
|
171
254
|
claudeShareLoading: {},
|
|
172
255
|
providerShareLoading: {},
|
|
256
|
+
providerSwitchInProgress: false,
|
|
257
|
+
pendingProviderSwitch: '',
|
|
173
258
|
installPackageManager: 'npm',
|
|
174
259
|
installCommandAction: 'install',
|
|
175
260
|
installRegistryPreset: 'default',
|
|
@@ -271,6 +356,22 @@ import { createSkillsMethods } from './modules/skills.methods.mjs';
|
|
|
271
356
|
codexDownloadLoading: false,
|
|
272
357
|
codexDownloadProgress: 0,
|
|
273
358
|
codexDownloadTimer: null,
|
|
359
|
+
settingsTab: 'backup',
|
|
360
|
+
sessionTrashItems: [],
|
|
361
|
+
sessionTrashVisibleCount: SESSION_TRASH_PAGE_SIZE,
|
|
362
|
+
sessionTrashTotalCount: 0,
|
|
363
|
+
sessionTrashCountLoadedOnce: false,
|
|
364
|
+
sessionTrashLoadedOnce: false,
|
|
365
|
+
sessionTrashLastLoadFailed: false,
|
|
366
|
+
sessionTrashCountRequestToken: 0,
|
|
367
|
+
sessionTrashListRequestToken: 0,
|
|
368
|
+
sessionTrashCountPendingOptions: null,
|
|
369
|
+
sessionTrashPendingOptions: null,
|
|
370
|
+
sessionTrashCountLoading: false,
|
|
371
|
+
sessionTrashLoading: false,
|
|
372
|
+
sessionTrashRestoring: {},
|
|
373
|
+
sessionTrashPurging: {},
|
|
374
|
+
sessionTrashClearing: false,
|
|
274
375
|
claudeImportLoading: false,
|
|
275
376
|
codexImportLoading: false,
|
|
276
377
|
codexAuthProfiles: [],
|
|
@@ -291,11 +392,13 @@ import { createSkillsMethods } from './modules/skills.methods.mjs';
|
|
|
291
392
|
proxyStarting: false,
|
|
292
393
|
proxyStopping: false,
|
|
293
394
|
proxyApplying: false,
|
|
294
|
-
showProxyAdvanced: false
|
|
395
|
+
showProxyAdvanced: false,
|
|
396
|
+
forceCompactLayout: false
|
|
295
397
|
}
|
|
296
398
|
},
|
|
297
399
|
mounted() {
|
|
298
400
|
this.initSessionStandalone();
|
|
401
|
+
this.updateCompactLayoutMode();
|
|
299
402
|
const savedSessionYolo = localStorage.getItem('codexmateSessionResumeYolo');
|
|
300
403
|
if (savedSessionYolo === '0' || savedSessionYolo === 'false') {
|
|
301
404
|
this.sessionResumeWithYolo = false;
|
|
@@ -303,7 +406,10 @@ import { createSkillsMethods } from './modules/skills.methods.mjs';
|
|
|
303
406
|
this.sessionResumeWithYolo = true;
|
|
304
407
|
}
|
|
305
408
|
this.restoreSessionFilterCache();
|
|
409
|
+
this.restoreSessionPinnedMap();
|
|
306
410
|
window.addEventListener('resize', this.onWindowResize);
|
|
411
|
+
window.addEventListener('keydown', this.handleGlobalKeydown);
|
|
412
|
+
window.addEventListener('beforeunload', this.handleBeforeUnload);
|
|
307
413
|
const savedConfigs = localStorage.getItem('claudeConfigs');
|
|
308
414
|
if (savedConfigs) {
|
|
309
415
|
try {
|
|
@@ -340,24 +446,104 @@ import { createSkillsMethods } from './modules/skills.methods.mjs';
|
|
|
340
446
|
this.loadAll();
|
|
341
447
|
},
|
|
342
448
|
beforeUnmount() {
|
|
343
|
-
this.
|
|
449
|
+
this.teardownSessionTabRender();
|
|
450
|
+
this.cancelScheduledSessionTabDeferredTeardown();
|
|
344
451
|
this.disconnectSessionPreviewHeaderResizeObserver();
|
|
345
452
|
window.removeEventListener('resize', this.onWindowResize);
|
|
453
|
+
window.removeEventListener('keydown', this.handleGlobalKeydown);
|
|
454
|
+
window.removeEventListener('beforeunload', this.handleBeforeUnload);
|
|
455
|
+
this.applyCompactLayoutClass(false);
|
|
346
456
|
this.sessionPreviewScrollEl = null;
|
|
347
457
|
this.sessionPreviewContainerEl = null;
|
|
348
458
|
this.sessionPreviewHeaderEl = null;
|
|
349
|
-
this.
|
|
459
|
+
this.clearSessionTimelineRefs();
|
|
350
460
|
},
|
|
351
461
|
|
|
352
462
|
computed: {
|
|
353
463
|
isSessionQueryEnabled() {
|
|
354
464
|
return isSessionQueryEnabled(this.sessionFilterSource);
|
|
355
465
|
},
|
|
466
|
+
activeSessionExportKey() {
|
|
467
|
+
return this.activeSession ? this.getSessionExportKey(this.activeSession) : '';
|
|
468
|
+
},
|
|
469
|
+
sortedSessionsList() {
|
|
470
|
+
const list = Array.isArray(this.sessionsList) ? this.sessionsList : [];
|
|
471
|
+
if (list.length === 0) return [];
|
|
472
|
+
const pinnedMap = (this.sessionPinnedMap && typeof this.sessionPinnedMap === 'object')
|
|
473
|
+
? this.sessionPinnedMap
|
|
474
|
+
: {};
|
|
475
|
+
let hasPinned = false;
|
|
476
|
+
const decorated = list.map((session, index) => {
|
|
477
|
+
const key = session ? this.getSessionExportKey(session) : '';
|
|
478
|
+
const rawPinnedAt = key ? pinnedMap[key] : 0;
|
|
479
|
+
const pinnedAt = Number.isFinite(Number(rawPinnedAt))
|
|
480
|
+
? Math.floor(Number(rawPinnedAt))
|
|
481
|
+
: 0;
|
|
482
|
+
const isPinned = pinnedAt > 0;
|
|
483
|
+
if (isPinned) {
|
|
484
|
+
hasPinned = true;
|
|
485
|
+
}
|
|
486
|
+
return { session, index, pinnedAt, isPinned };
|
|
487
|
+
});
|
|
488
|
+
if (!hasPinned) return list;
|
|
489
|
+
decorated.sort((a, b) => {
|
|
490
|
+
if (a.isPinned !== b.isPinned) return a.isPinned ? -1 : 1;
|
|
491
|
+
if (a.isPinned && a.pinnedAt !== b.pinnedAt) return b.pinnedAt - a.pinnedAt;
|
|
492
|
+
return a.index - b.index;
|
|
493
|
+
});
|
|
494
|
+
return decorated.map(item => item.session);
|
|
495
|
+
},
|
|
496
|
+
activeSessionVisibleMessages() {
|
|
497
|
+
if (this.mainTab !== 'sessions' || !this.sessionPreviewRenderEnabled) {
|
|
498
|
+
return [];
|
|
499
|
+
}
|
|
500
|
+
const list = Array.isArray(this.activeSessionMessages) ? this.activeSessionMessages : [];
|
|
501
|
+
const rawCount = Number(this.sessionPreviewVisibleCount);
|
|
502
|
+
const visibleCount = Number.isFinite(rawCount)
|
|
503
|
+
? Math.max(0, Math.floor(rawCount))
|
|
504
|
+
: 0;
|
|
505
|
+
if (visibleCount <= 0) {
|
|
506
|
+
if (!list.length) return [];
|
|
507
|
+
// Defensive fallback: avoid getting stuck in "正在渲染会话内容..."
|
|
508
|
+
// when visible count has not been primed yet.
|
|
509
|
+
return list.slice(0, Math.min(8, list.length));
|
|
510
|
+
}
|
|
511
|
+
if (visibleCount >= list.length) return list;
|
|
512
|
+
return list.slice(0, visibleCount);
|
|
513
|
+
},
|
|
514
|
+
canLoadMoreSessionMessages() {
|
|
515
|
+
if (this.mainTab !== 'sessions' || !this.sessionPreviewRenderEnabled) {
|
|
516
|
+
return false;
|
|
517
|
+
}
|
|
518
|
+
const total = Array.isArray(this.activeSessionMessages) ? this.activeSessionMessages.length : 0;
|
|
519
|
+
const visible = Array.isArray(this.activeSessionVisibleMessages) ? this.activeSessionVisibleMessages.length : 0;
|
|
520
|
+
return total > visible;
|
|
521
|
+
},
|
|
522
|
+
sessionPreviewRemainingCount() {
|
|
523
|
+
const total = Array.isArray(this.activeSessionMessages) ? this.activeSessionMessages.length : 0;
|
|
524
|
+
const visible = Array.isArray(this.activeSessionVisibleMessages) ? this.activeSessionVisibleMessages.length : 0;
|
|
525
|
+
return Math.max(0, total - visible);
|
|
526
|
+
},
|
|
356
527
|
sessionTimelineNodes() {
|
|
357
|
-
|
|
528
|
+
if (this.mainTab !== 'sessions' || !this.sessionPreviewRenderEnabled) {
|
|
529
|
+
return [];
|
|
530
|
+
}
|
|
531
|
+
return buildSessionTimelineNodes(this.activeSessionVisibleMessages, {
|
|
358
532
|
getKey: (message, index) => this.getRecordRenderKey(message, index)
|
|
359
533
|
});
|
|
360
534
|
},
|
|
535
|
+
sessionTimelineNodeKeyMap() {
|
|
536
|
+
const nodes = Array.isArray(this.sessionTimelineNodes) ? this.sessionTimelineNodes : [];
|
|
537
|
+
if (!nodes.length) {
|
|
538
|
+
return Object.create(null);
|
|
539
|
+
}
|
|
540
|
+
const map = Object.create(null);
|
|
541
|
+
for (const node of nodes) {
|
|
542
|
+
if (!node || !node.key) continue;
|
|
543
|
+
map[node.key] = true;
|
|
544
|
+
}
|
|
545
|
+
return map;
|
|
546
|
+
},
|
|
361
547
|
sessionTimelineActiveTitle() {
|
|
362
548
|
if (!this.sessionTimelineActiveKey) return '';
|
|
363
549
|
const nodes = Array.isArray(this.sessionTimelineNodes) ? this.sessionTimelineNodes : [];
|
|
@@ -370,6 +556,15 @@ import { createSkillsMethods } from './modules/skills.methods.mjs';
|
|
|
370
556
|
}
|
|
371
557
|
return '当前来源暂不支持关键词检索';
|
|
372
558
|
},
|
|
559
|
+
agentsDiffHasChanges() {
|
|
560
|
+
if (this.agentsDiffTruncated) {
|
|
561
|
+
return !!this.agentsDiffHasChangesValue;
|
|
562
|
+
}
|
|
563
|
+
const stats = this.agentsDiffStats || {};
|
|
564
|
+
const added = Number(stats.added || 0);
|
|
565
|
+
const removed = Number(stats.removed || 0);
|
|
566
|
+
return added > 0 || removed > 0;
|
|
567
|
+
},
|
|
373
568
|
claudeModelHasList() {
|
|
374
569
|
return Array.isArray(this.claudeModels) && this.claudeModels.length > 0;
|
|
375
570
|
},
|
|
@@ -409,6 +604,29 @@ import { createSkillsMethods } from './modules/skills.methods.mjs';
|
|
|
409
604
|
installRegistryPreview() {
|
|
410
605
|
return this.resolveInstallRegistryUrl(this.installRegistryPreset, this.installRegistryCustom);
|
|
411
606
|
},
|
|
607
|
+
visibleSessionTrashItems() {
|
|
608
|
+
const items = Array.isArray(this.sessionTrashItems) ? this.sessionTrashItems : [];
|
|
609
|
+
const visibleCount = Number(this.sessionTrashVisibleCount);
|
|
610
|
+
const safeVisibleCount = Number.isFinite(visibleCount) && visibleCount > 0
|
|
611
|
+
? Math.floor(visibleCount)
|
|
612
|
+
: SESSION_TRASH_PAGE_SIZE;
|
|
613
|
+
return items.slice(0, safeVisibleCount);
|
|
614
|
+
},
|
|
615
|
+
sessionTrashHasMoreItems() {
|
|
616
|
+
const totalItems = Array.isArray(this.sessionTrashItems) ? this.sessionTrashItems.length : 0;
|
|
617
|
+
return this.visibleSessionTrashItems.length < totalItems;
|
|
618
|
+
},
|
|
619
|
+
sessionTrashHiddenCount() {
|
|
620
|
+
const totalItems = Array.isArray(this.sessionTrashItems) ? this.sessionTrashItems.length : 0;
|
|
621
|
+
return Math.max(0, totalItems - this.visibleSessionTrashItems.length);
|
|
622
|
+
},
|
|
623
|
+
sessionTrashCount() {
|
|
624
|
+
const totalCount = Number(this.sessionTrashTotalCount);
|
|
625
|
+
if (Number.isFinite(totalCount) && totalCount >= 0) {
|
|
626
|
+
return Math.max(0, Math.floor(totalCount));
|
|
627
|
+
}
|
|
628
|
+
return Array.isArray(this.sessionTrashItems) ? this.sessionTrashItems.length : 0;
|
|
629
|
+
},
|
|
412
630
|
...createSkillsComputed(),
|
|
413
631
|
|
|
414
632
|
...createConfigModeComputed(),
|
|
@@ -542,7 +760,8 @@ import { createSkillsMethods } from './modules/skills.methods.mjs';
|
|
|
542
760
|
}
|
|
543
761
|
},
|
|
544
762
|
|
|
545
|
-
async loadModelsForProvider(providerName) {
|
|
763
|
+
async loadModelsForProvider(providerName, options = {}) {
|
|
764
|
+
const silentError = !!options.silentError;
|
|
546
765
|
this.codexModelsLoading = true;
|
|
547
766
|
if (!providerName) {
|
|
548
767
|
this.models = [];
|
|
@@ -560,7 +779,9 @@ import { createSkillsMethods } from './modules/skills.methods.mjs';
|
|
|
560
779
|
return;
|
|
561
780
|
}
|
|
562
781
|
if (res.error) {
|
|
563
|
-
|
|
782
|
+
if (!silentError) {
|
|
783
|
+
this.showMessage('获取模型列表失败', 'error');
|
|
784
|
+
}
|
|
564
785
|
this.models = [];
|
|
565
786
|
this.modelsSource = 'error';
|
|
566
787
|
this.modelsHasCurrent = true;
|
|
@@ -571,7 +792,9 @@ import { createSkillsMethods } from './modules/skills.methods.mjs';
|
|
|
571
792
|
this.modelsSource = res.source || 'remote';
|
|
572
793
|
this.modelsHasCurrent = !!this.currentModel && list.includes(this.currentModel);
|
|
573
794
|
} catch (e) {
|
|
574
|
-
|
|
795
|
+
if (!silentError) {
|
|
796
|
+
this.showMessage('获取模型列表失败', 'error');
|
|
797
|
+
}
|
|
575
798
|
this.models = [];
|
|
576
799
|
this.modelsSource = 'error';
|
|
577
800
|
this.modelsHasCurrent = true;
|
|
@@ -603,15 +826,58 @@ import { createSkillsMethods } from './modules/skills.methods.mjs';
|
|
|
603
826
|
return findDuplicateClaudeConfigName(this.claudeConfigs, config);
|
|
604
827
|
},
|
|
605
828
|
|
|
606
|
-
|
|
607
|
-
const
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
829
|
+
mergeClaudeConfig(existing = {}, updates = {}) {
|
|
830
|
+
const previous = this.normalizeClaudeConfig(existing);
|
|
831
|
+
const next = this.normalizeClaudeConfig({ ...existing, ...updates });
|
|
832
|
+
const externalCredentialType = next.apiKey
|
|
833
|
+
? ''
|
|
834
|
+
: (next.externalCredentialType || previous.externalCredentialType || '');
|
|
835
|
+
return {
|
|
836
|
+
apiKey: next.apiKey,
|
|
837
|
+
baseUrl: next.baseUrl,
|
|
838
|
+
model: next.model || previous.model || 'glm-4.7',
|
|
839
|
+
hasKey: !!(next.apiKey || externalCredentialType),
|
|
840
|
+
externalCredentialType
|
|
841
|
+
};
|
|
842
|
+
},
|
|
843
|
+
|
|
844
|
+
buildClaudeImportedConfigName(baseUrl) {
|
|
845
|
+
const normalizedUrl = typeof baseUrl === 'string' ? baseUrl.trim() : '';
|
|
846
|
+
if (!normalizedUrl) return '导入配置';
|
|
847
|
+
try {
|
|
848
|
+
const parsed = new URL(normalizedUrl);
|
|
849
|
+
const host = typeof parsed.host === 'string' ? parsed.host.trim() : '';
|
|
850
|
+
if (host) return `导入-${host}`;
|
|
851
|
+
} catch (_) {
|
|
852
|
+
// keep generic fallback name
|
|
613
853
|
}
|
|
854
|
+
return '导入配置';
|
|
855
|
+
},
|
|
856
|
+
|
|
857
|
+
ensureClaudeConfigFromSettings(env = {}) {
|
|
858
|
+
const normalized = this.normalizeClaudeSettingsEnv(env);
|
|
859
|
+
const hasCredential = !!(normalized.apiKey || normalized.authToken || normalized.useKey);
|
|
860
|
+
if (!normalized.baseUrl || !hasCredential) return '';
|
|
861
|
+
|
|
862
|
+
const duplicateName = this.findDuplicateClaudeConfigName(normalized);
|
|
863
|
+
if (duplicateName) return duplicateName;
|
|
864
|
+
|
|
865
|
+
const preferredName = this.buildClaudeImportedConfigName(normalized.baseUrl);
|
|
866
|
+
let candidateName = preferredName;
|
|
867
|
+
let suffix = 2;
|
|
868
|
+
while (this.claudeConfigs[candidateName]) {
|
|
869
|
+
candidateName = `${preferredName}-${suffix}`;
|
|
870
|
+
suffix += 1;
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
this.claudeConfigs[candidateName] = this.mergeClaudeConfig({}, normalized);
|
|
874
|
+
this.saveClaudeConfigs();
|
|
875
|
+
return candidateName;
|
|
876
|
+
},
|
|
877
|
+
|
|
878
|
+
async refreshClaudeSelectionFromSettings(options = {}) {
|
|
614
879
|
const silent = !!options.silent;
|
|
880
|
+
const silentModelError = !!options.silentModelError || silent;
|
|
615
881
|
try {
|
|
616
882
|
const res = await api('get-claude-settings');
|
|
617
883
|
if (res && res.error) {
|
|
@@ -625,7 +891,18 @@ import { createSkillsMethods } from './modules/skills.methods.mjs';
|
|
|
625
891
|
if (this.currentClaudeConfig !== matchName) {
|
|
626
892
|
this.currentClaudeConfig = matchName;
|
|
627
893
|
}
|
|
628
|
-
this.refreshClaudeModelContext();
|
|
894
|
+
this.refreshClaudeModelContext({ silentError: silentModelError });
|
|
895
|
+
return;
|
|
896
|
+
}
|
|
897
|
+
const importedName = this.ensureClaudeConfigFromSettings((res && res.env) || {});
|
|
898
|
+
if (importedName) {
|
|
899
|
+
if (this.currentClaudeConfig !== importedName) {
|
|
900
|
+
this.currentClaudeConfig = importedName;
|
|
901
|
+
}
|
|
902
|
+
this.refreshClaudeModelContext({ silentError: silentModelError });
|
|
903
|
+
if (!silent) {
|
|
904
|
+
this.showMessage(`检测到外部 Claude 配置,已自动导入:${importedName}`, 'success');
|
|
905
|
+
}
|
|
629
906
|
return;
|
|
630
907
|
}
|
|
631
908
|
this.currentClaudeConfig = '';
|
|
@@ -649,9 +926,9 @@ import { createSkillsMethods } from './modules/skills.methods.mjs';
|
|
|
649
926
|
this.currentClaudeModel = config && config.model ? config.model : '';
|
|
650
927
|
},
|
|
651
928
|
|
|
652
|
-
refreshClaudeModelContext() {
|
|
929
|
+
refreshClaudeModelContext(options = {}) {
|
|
653
930
|
this.syncClaudeModelFromConfig();
|
|
654
|
-
this.loadClaudeModels();
|
|
931
|
+
return this.loadClaudeModels(options);
|
|
655
932
|
},
|
|
656
933
|
|
|
657
934
|
resetClaudeModelsState() {
|
|
@@ -666,7 +943,8 @@ import { createSkillsMethods } from './modules/skills.methods.mjs';
|
|
|
666
943
|
this.claudeModelsHasCurrent = !!currentModel && this.claudeModels.includes(currentModel);
|
|
667
944
|
},
|
|
668
945
|
|
|
669
|
-
async loadClaudeModels() {
|
|
946
|
+
async loadClaudeModels(options = {}) {
|
|
947
|
+
const silentError = !!options.silentError;
|
|
670
948
|
const config = this.getCurrentClaudeConfig();
|
|
671
949
|
if (!config) {
|
|
672
950
|
this.resetClaudeModelsState();
|
|
@@ -674,11 +952,20 @@ import { createSkillsMethods } from './modules/skills.methods.mjs';
|
|
|
674
952
|
}
|
|
675
953
|
const baseUrl = (config.baseUrl || '').trim();
|
|
676
954
|
const apiKey = (config.apiKey || '').trim();
|
|
955
|
+
const externalCredentialType = typeof config.externalCredentialType === 'string'
|
|
956
|
+
? config.externalCredentialType.trim()
|
|
957
|
+
: '';
|
|
677
958
|
|
|
678
959
|
if (!baseUrl) {
|
|
679
960
|
this.resetClaudeModelsState();
|
|
680
961
|
return;
|
|
681
962
|
}
|
|
963
|
+
if (!apiKey && externalCredentialType) {
|
|
964
|
+
this.claudeModels = [];
|
|
965
|
+
this.claudeModelsSource = 'unlimited';
|
|
966
|
+
this.claudeModelsHasCurrent = true;
|
|
967
|
+
return;
|
|
968
|
+
}
|
|
682
969
|
|
|
683
970
|
this.claudeModelsLoading = true;
|
|
684
971
|
try {
|
|
@@ -690,7 +977,9 @@ import { createSkillsMethods } from './modules/skills.methods.mjs';
|
|
|
690
977
|
return;
|
|
691
978
|
}
|
|
692
979
|
if (res.error) {
|
|
693
|
-
|
|
980
|
+
if (!silentError) {
|
|
981
|
+
this.showMessage('获取模型列表失败', 'error');
|
|
982
|
+
}
|
|
694
983
|
this.claudeModels = [];
|
|
695
984
|
this.claudeModelsSource = 'error';
|
|
696
985
|
this.claudeModelsHasCurrent = true;
|
|
@@ -701,7 +990,9 @@ import { createSkillsMethods } from './modules/skills.methods.mjs';
|
|
|
701
990
|
this.claudeModelsSource = res.source || 'remote';
|
|
702
991
|
this.updateClaudeModelsCurrent();
|
|
703
992
|
} catch (e) {
|
|
704
|
-
|
|
993
|
+
if (!silentError) {
|
|
994
|
+
this.showMessage('获取模型列表失败', 'error');
|
|
995
|
+
}
|
|
705
996
|
this.claudeModels = [];
|
|
706
997
|
this.claudeModelsSource = 'error';
|
|
707
998
|
this.claudeModelsHasCurrent = true;
|
|
@@ -727,21 +1018,413 @@ import { createSkillsMethods } from './modules/skills.methods.mjs';
|
|
|
727
1018
|
const normalizedMode = typeof mode === 'string'
|
|
728
1019
|
? mode.trim().toLowerCase()
|
|
729
1020
|
: '';
|
|
730
|
-
this.mainTab = 'config';
|
|
731
1021
|
this.configMode = CONFIG_MODE_SET.has(normalizedMode) ? normalizedMode : 'codex';
|
|
732
|
-
if (this.
|
|
733
|
-
this.
|
|
1022
|
+
if (this.mainTab === 'config') {
|
|
1023
|
+
if (this.configMode === 'claude') {
|
|
1024
|
+
const expectedMainTab = 'config';
|
|
1025
|
+
const expectedConfigMode = 'claude';
|
|
1026
|
+
const refresh = () => {
|
|
1027
|
+
if (this.mainTab !== expectedMainTab || this.configMode !== expectedConfigMode) {
|
|
1028
|
+
return;
|
|
1029
|
+
}
|
|
1030
|
+
this.refreshClaudeModelContext();
|
|
1031
|
+
};
|
|
1032
|
+
if (typeof this.scheduleAfterFrame === 'function') {
|
|
1033
|
+
this.scheduleAfterFrame(refresh);
|
|
1034
|
+
} else {
|
|
1035
|
+
refresh();
|
|
1036
|
+
}
|
|
1037
|
+
}
|
|
1038
|
+
this.scheduleAfterFrame(() => {
|
|
1039
|
+
this.clearMainTabSwitchIntent('config');
|
|
1040
|
+
});
|
|
1041
|
+
return;
|
|
734
1042
|
}
|
|
1043
|
+
this.switchMainTab('config');
|
|
735
1044
|
},
|
|
736
1045
|
|
|
1046
|
+
ensureMainTabSwitchState() {
|
|
1047
|
+
if (this.__mainTabSwitchState) {
|
|
1048
|
+
return this.__mainTabSwitchState;
|
|
1049
|
+
}
|
|
1050
|
+
this.__mainTabSwitchState = {
|
|
1051
|
+
intent: '',
|
|
1052
|
+
pendingTarget: '',
|
|
1053
|
+
ticket: 0
|
|
1054
|
+
};
|
|
1055
|
+
return this.__mainTabSwitchState;
|
|
1056
|
+
},
|
|
1057
|
+
ensureImmediateNavDomState() {
|
|
1058
|
+
if (typeof document === 'undefined') {
|
|
1059
|
+
return {
|
|
1060
|
+
navNodes: [],
|
|
1061
|
+
sessionPanelEl: null
|
|
1062
|
+
};
|
|
1063
|
+
}
|
|
1064
|
+
if (!this.__immediateNavDomState) {
|
|
1065
|
+
this.__immediateNavDomState = {
|
|
1066
|
+
navNodes: [],
|
|
1067
|
+
sessionPanelEl: null
|
|
1068
|
+
};
|
|
1069
|
+
}
|
|
1070
|
+
const state = this.__immediateNavDomState;
|
|
1071
|
+
const needsNavRefresh = !Array.isArray(state.navNodes)
|
|
1072
|
+
|| !state.navNodes.length
|
|
1073
|
+
|| state.navNodes.some((node) => !node || !node.isConnected);
|
|
1074
|
+
if (needsNavRefresh) {
|
|
1075
|
+
state.navNodes = Array.from(document.querySelectorAll('[data-main-tab]'));
|
|
1076
|
+
}
|
|
1077
|
+
if (!state.sessionPanelEl || !state.sessionPanelEl.isConnected) {
|
|
1078
|
+
state.sessionPanelEl = document.getElementById('panel-sessions');
|
|
1079
|
+
}
|
|
1080
|
+
return state;
|
|
1081
|
+
},
|
|
1082
|
+
setMainTabSwitchIntent(tab) {
|
|
1083
|
+
const normalizedTab = typeof tab === 'string'
|
|
1084
|
+
? tab.trim().toLowerCase()
|
|
1085
|
+
: '';
|
|
1086
|
+
if (!normalizedTab) return;
|
|
1087
|
+
const state = this.ensureMainTabSwitchState();
|
|
1088
|
+
state.intent = normalizedTab;
|
|
1089
|
+
},
|
|
1090
|
+
applyImmediateNavIntent(tab, configMode = '') {
|
|
1091
|
+
if (typeof document === 'undefined') return;
|
|
1092
|
+
const normalizedTab = typeof tab === 'string' ? tab.trim().toLowerCase() : '';
|
|
1093
|
+
if (!normalizedTab) return;
|
|
1094
|
+
const normalizedMode = typeof configMode === 'string' ? configMode.trim().toLowerCase() : '';
|
|
1095
|
+
const domState = this.ensureImmediateNavDomState();
|
|
1096
|
+
const nodes = Array.isArray(domState.navNodes) ? domState.navNodes : [];
|
|
1097
|
+
for (const node of nodes) {
|
|
1098
|
+
if (!node || !node.classList) continue;
|
|
1099
|
+
const nodeTab = String(node.getAttribute('data-main-tab') || '').trim().toLowerCase();
|
|
1100
|
+
const nodeMode = String(node.getAttribute('data-config-mode') || '').trim().toLowerCase();
|
|
1101
|
+
let shouldActivate = nodeTab === normalizedTab;
|
|
1102
|
+
if (shouldActivate && normalizedTab === 'config') {
|
|
1103
|
+
shouldActivate = nodeMode ? nodeMode === normalizedMode : false;
|
|
1104
|
+
}
|
|
1105
|
+
node.classList.toggle('nav-intent-active', !!shouldActivate);
|
|
1106
|
+
node.classList.toggle('nav-intent-inactive', !shouldActivate);
|
|
1107
|
+
}
|
|
1108
|
+
},
|
|
1109
|
+
clearImmediateNavIntent() {
|
|
1110
|
+
if (typeof document === 'undefined') return;
|
|
1111
|
+
const domState = this.ensureImmediateNavDomState();
|
|
1112
|
+
const nodes = Array.isArray(domState.navNodes) ? domState.navNodes : [];
|
|
1113
|
+
for (const node of nodes) {
|
|
1114
|
+
if (!node || !node.classList) continue;
|
|
1115
|
+
node.classList.remove('nav-intent-active');
|
|
1116
|
+
node.classList.remove('nav-intent-inactive');
|
|
1117
|
+
}
|
|
1118
|
+
},
|
|
1119
|
+
setSessionPanelFastHidden(hidden) {
|
|
1120
|
+
if (typeof document === 'undefined') return;
|
|
1121
|
+
const domState = this.ensureImmediateNavDomState();
|
|
1122
|
+
const panel = domState.sessionPanelEl;
|
|
1123
|
+
if (!panel || !panel.classList) return;
|
|
1124
|
+
panel.classList.toggle('session-panel-fast-hidden', !!hidden);
|
|
1125
|
+
},
|
|
1126
|
+
isSessionPanelFastHidden() {
|
|
1127
|
+
if (typeof document === 'undefined') return false;
|
|
1128
|
+
const domState = this.ensureImmediateNavDomState();
|
|
1129
|
+
const panel = domState.sessionPanelEl;
|
|
1130
|
+
return !!(panel && panel.classList && panel.classList.contains('session-panel-fast-hidden'));
|
|
1131
|
+
},
|
|
1132
|
+
recordPointerNavCommit(kind, value) {
|
|
1133
|
+
const normalizedKind = typeof kind === 'string' ? kind.trim().toLowerCase() : '';
|
|
1134
|
+
const normalizedValue = typeof value === 'string' ? value.trim().toLowerCase() : '';
|
|
1135
|
+
if (!normalizedKind || !normalizedValue) {
|
|
1136
|
+
this.__pointerNavCommitState = null;
|
|
1137
|
+
return;
|
|
1138
|
+
}
|
|
1139
|
+
this.__pointerNavCommitState = {
|
|
1140
|
+
kind: normalizedKind,
|
|
1141
|
+
value: normalizedValue,
|
|
1142
|
+
at: Date.now()
|
|
1143
|
+
};
|
|
1144
|
+
},
|
|
1145
|
+
consumePointerNavCommit(kind, value) {
|
|
1146
|
+
const normalizedKind = typeof kind === 'string' ? kind.trim().toLowerCase() : '';
|
|
1147
|
+
const normalizedValue = typeof value === 'string' ? value.trim().toLowerCase() : '';
|
|
1148
|
+
const state = this.__pointerNavCommitState;
|
|
1149
|
+
this.__pointerNavCommitState = null;
|
|
1150
|
+
if (!state || !normalizedKind || !normalizedValue) {
|
|
1151
|
+
return false;
|
|
1152
|
+
}
|
|
1153
|
+
if (state.kind !== normalizedKind || state.value !== normalizedValue) {
|
|
1154
|
+
return false;
|
|
1155
|
+
}
|
|
1156
|
+
return (Date.now() - Number(state.at || 0)) <= 1000;
|
|
1157
|
+
},
|
|
1158
|
+
onMainTabPointerDown(tab) {
|
|
1159
|
+
const event = arguments.length > 1 ? arguments[1] : null;
|
|
1160
|
+
if (event && typeof event.button === 'number' && event.button !== 0) {
|
|
1161
|
+
return;
|
|
1162
|
+
}
|
|
1163
|
+
const normalizedTab = typeof tab === 'string' ? tab.trim().toLowerCase() : '';
|
|
1164
|
+
if (!normalizedTab) return;
|
|
1165
|
+
this.setMainTabSwitchIntent(normalizedTab);
|
|
1166
|
+
this.applyImmediateNavIntent(normalizedTab);
|
|
1167
|
+
const shouldHideSessionPanel = this.mainTab === 'sessions' && normalizedTab !== 'sessions';
|
|
1168
|
+
this.setSessionPanelFastHidden(shouldHideSessionPanel);
|
|
1169
|
+
const pointerType = event && typeof event.pointerType === 'string'
|
|
1170
|
+
? event.pointerType.trim().toLowerCase()
|
|
1171
|
+
: '';
|
|
1172
|
+
if (pointerType === 'touch') {
|
|
1173
|
+
return;
|
|
1174
|
+
}
|
|
1175
|
+
this.recordPointerNavCommit('main', normalizedTab);
|
|
1176
|
+
this.switchMainTab(normalizedTab);
|
|
1177
|
+
},
|
|
1178
|
+
onConfigTabPointerDown(mode) {
|
|
1179
|
+
const event = arguments.length > 1 ? arguments[1] : null;
|
|
1180
|
+
if (event && typeof event.button === 'number' && event.button !== 0) {
|
|
1181
|
+
return;
|
|
1182
|
+
}
|
|
1183
|
+
const normalizedMode = typeof mode === 'string' ? mode.trim().toLowerCase() : '';
|
|
1184
|
+
if (!normalizedMode) return;
|
|
1185
|
+
this.setMainTabSwitchIntent('config');
|
|
1186
|
+
this.applyImmediateNavIntent('config', normalizedMode);
|
|
1187
|
+
const shouldHideSessionPanel = this.mainTab === 'sessions';
|
|
1188
|
+
this.setSessionPanelFastHidden(shouldHideSessionPanel);
|
|
1189
|
+
const pointerType = event && typeof event.pointerType === 'string'
|
|
1190
|
+
? event.pointerType.trim().toLowerCase()
|
|
1191
|
+
: '';
|
|
1192
|
+
if (pointerType === 'touch') {
|
|
1193
|
+
return;
|
|
1194
|
+
}
|
|
1195
|
+
this.recordPointerNavCommit('config', normalizedMode);
|
|
1196
|
+
this.switchConfigMode(normalizedMode);
|
|
1197
|
+
},
|
|
1198
|
+
onMainTabClick(tab) {
|
|
1199
|
+
const normalizedTab = typeof tab === 'string' ? tab.trim().toLowerCase() : '';
|
|
1200
|
+
if (!normalizedTab) return;
|
|
1201
|
+
if (this.consumePointerNavCommit('main', normalizedTab)) return;
|
|
1202
|
+
this.switchMainTab(normalizedTab);
|
|
1203
|
+
},
|
|
1204
|
+
onConfigTabClick(mode) {
|
|
1205
|
+
const normalizedMode = typeof mode === 'string' ? mode.trim().toLowerCase() : '';
|
|
1206
|
+
if (!normalizedMode) return;
|
|
1207
|
+
if (this.consumePointerNavCommit('config', normalizedMode)) return;
|
|
1208
|
+
this.switchConfigMode(normalizedMode);
|
|
1209
|
+
},
|
|
1210
|
+
clearMainTabSwitchIntent(expectedTab = '') {
|
|
1211
|
+
const state = this.ensureMainTabSwitchState();
|
|
1212
|
+
if (expectedTab && state.intent && state.intent !== expectedTab) {
|
|
1213
|
+
return;
|
|
1214
|
+
}
|
|
1215
|
+
state.intent = '';
|
|
1216
|
+
state.pendingTarget = '';
|
|
1217
|
+
this.clearImmediateNavIntent();
|
|
1218
|
+
this.setSessionPanelFastHidden(false);
|
|
1219
|
+
},
|
|
1220
|
+
getMainTabForNav() {
|
|
1221
|
+
const state = this.ensureMainTabSwitchState();
|
|
1222
|
+
return state.intent || this.mainTab;
|
|
1223
|
+
},
|
|
1224
|
+
isMainTabNavActive(tab) {
|
|
1225
|
+
return this.getMainTabForNav() === tab;
|
|
1226
|
+
},
|
|
1227
|
+
isConfigModeNavActive(mode) {
|
|
1228
|
+
return this.isMainTabNavActive('config') && this.configMode === mode;
|
|
1229
|
+
},
|
|
737
1230
|
switchMainTab(tab) {
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
1231
|
+
const normalizedTab = typeof tab === 'string'
|
|
1232
|
+
? tab.trim().toLowerCase()
|
|
1233
|
+
: '';
|
|
1234
|
+
const targetTab = normalizedTab || tab;
|
|
1235
|
+
if (!targetTab) return;
|
|
1236
|
+
if (targetTab === 'sessions') {
|
|
1237
|
+
this.cancelScheduledSessionTabDeferredTeardown();
|
|
741
1238
|
}
|
|
742
|
-
|
|
743
|
-
|
|
1239
|
+
|
|
1240
|
+
this.setMainTabSwitchIntent(targetTab);
|
|
1241
|
+
if (targetTab === 'config') {
|
|
1242
|
+
this.applyImmediateNavIntent('config', this.configMode);
|
|
1243
|
+
} else {
|
|
1244
|
+
this.applyImmediateNavIntent(targetTab);
|
|
1245
|
+
}
|
|
1246
|
+
|
|
1247
|
+
const previousTab = this.mainTab;
|
|
1248
|
+
const switchState = this.ensureMainTabSwitchState();
|
|
1249
|
+
if (targetTab === previousTab) {
|
|
1250
|
+
switchState.ticket += 1;
|
|
1251
|
+
switchState.pendingTarget = '';
|
|
1252
|
+
this.scheduleAfterFrame(() => {
|
|
1253
|
+
this.clearMainTabSwitchIntent(normalizedTab);
|
|
1254
|
+
});
|
|
1255
|
+
return;
|
|
1256
|
+
}
|
|
1257
|
+
const isLeavingSessions = previousTab === 'sessions' && targetTab !== 'sessions';
|
|
1258
|
+
const shouldDeferApply = isLeavingSessions;
|
|
1259
|
+
if (isLeavingSessions && !this.isSessionPanelFastHidden()) {
|
|
1260
|
+
this.setSessionPanelFastHidden(true);
|
|
1261
|
+
}
|
|
1262
|
+
if (!shouldDeferApply) {
|
|
1263
|
+
switchState.ticket += 1;
|
|
1264
|
+
switchState.pendingTarget = '';
|
|
1265
|
+
const result = switchMainTabHelper.call(this, targetTab);
|
|
1266
|
+
this.scheduleAfterFrame(() => {
|
|
1267
|
+
this.clearMainTabSwitchIntent(normalizedTab);
|
|
1268
|
+
});
|
|
1269
|
+
return result;
|
|
1270
|
+
}
|
|
1271
|
+
|
|
1272
|
+
const ticket = ++switchState.ticket;
|
|
1273
|
+
switchState.pendingTarget = targetTab;
|
|
1274
|
+
this.scheduleAfterFrame(() => {
|
|
1275
|
+
const liveState = this.ensureMainTabSwitchState();
|
|
1276
|
+
if (ticket !== liveState.ticket) return;
|
|
1277
|
+
const pendingTarget = liveState.pendingTarget || targetTab;
|
|
1278
|
+
liveState.pendingTarget = '';
|
|
1279
|
+
switchMainTabHelper.call(this, pendingTarget);
|
|
1280
|
+
this.clearMainTabSwitchIntent(normalizedTab);
|
|
1281
|
+
});
|
|
1282
|
+
},
|
|
1283
|
+
|
|
1284
|
+
scheduleAfterFrame(task) {
|
|
1285
|
+
const callback = typeof task === 'function' ? task : () => {};
|
|
1286
|
+
if (typeof requestAnimationFrame === 'function') {
|
|
1287
|
+
requestAnimationFrame(callback);
|
|
1288
|
+
return;
|
|
1289
|
+
}
|
|
1290
|
+
setTimeout(callback, 16);
|
|
1291
|
+
},
|
|
1292
|
+
scheduleIdleTask(task, timeoutMs = 160) {
|
|
1293
|
+
const callback = typeof task === 'function' ? task : () => {};
|
|
1294
|
+
const timeout = Number.isFinite(timeoutMs)
|
|
1295
|
+
? Math.max(16, Math.floor(timeoutMs))
|
|
1296
|
+
: 160;
|
|
1297
|
+
if (typeof requestIdleCallback === 'function') {
|
|
1298
|
+
const id = requestIdleCallback(callback, { timeout });
|
|
1299
|
+
return {
|
|
1300
|
+
type: 'idle',
|
|
1301
|
+
id
|
|
1302
|
+
};
|
|
744
1303
|
}
|
|
1304
|
+
const id = setTimeout(callback, timeout);
|
|
1305
|
+
return {
|
|
1306
|
+
type: 'timeout',
|
|
1307
|
+
id
|
|
1308
|
+
};
|
|
1309
|
+
},
|
|
1310
|
+
cancelIdleTask(handle) {
|
|
1311
|
+
if (!handle || typeof handle !== 'object') return;
|
|
1312
|
+
const type = handle.type;
|
|
1313
|
+
const id = handle.id;
|
|
1314
|
+
if (type === 'idle') {
|
|
1315
|
+
if (typeof cancelIdleCallback === 'function') {
|
|
1316
|
+
cancelIdleCallback(id);
|
|
1317
|
+
} else {
|
|
1318
|
+
clearTimeout(id);
|
|
1319
|
+
}
|
|
1320
|
+
return;
|
|
1321
|
+
}
|
|
1322
|
+
if (type === 'timeout') {
|
|
1323
|
+
clearTimeout(id);
|
|
1324
|
+
}
|
|
1325
|
+
},
|
|
1326
|
+
scheduleSessionTabDeferredTeardown(task) {
|
|
1327
|
+
const callback = typeof task === 'function' ? task : () => {};
|
|
1328
|
+
this.cancelScheduledSessionTabDeferredTeardown();
|
|
1329
|
+
this.__sessionTabDeferredTeardownHandle = this.scheduleIdleTask(() => {
|
|
1330
|
+
this.__sessionTabDeferredTeardownHandle = null;
|
|
1331
|
+
callback();
|
|
1332
|
+
}, 180);
|
|
1333
|
+
},
|
|
1334
|
+
cancelScheduledSessionTabDeferredTeardown() {
|
|
1335
|
+
const handle = this.__sessionTabDeferredTeardownHandle || null;
|
|
1336
|
+
if (!handle) return;
|
|
1337
|
+
this.cancelIdleTask(handle);
|
|
1338
|
+
this.__sessionTabDeferredTeardownHandle = null;
|
|
1339
|
+
},
|
|
1340
|
+
|
|
1341
|
+
resetSessionPreviewMessageRender() {
|
|
1342
|
+
this.sessionPreviewVisibleCount = 0;
|
|
1343
|
+
this.invalidateSessionTimelineMeasurementCache();
|
|
1344
|
+
},
|
|
1345
|
+
|
|
1346
|
+
resetSessionDetailPagination() {
|
|
1347
|
+
const initialLimit = Number.isFinite(this.sessionDetailInitialMessageLimit)
|
|
1348
|
+
? Math.max(1, Math.floor(this.sessionDetailInitialMessageLimit))
|
|
1349
|
+
: 80;
|
|
1350
|
+
this.sessionDetailMessageLimit = initialLimit;
|
|
1351
|
+
this.sessionPreviewPendingVisibleCount = 0;
|
|
1352
|
+
},
|
|
1353
|
+
|
|
1354
|
+
primeSessionPreviewMessageRender() {
|
|
1355
|
+
this.sessionPreviewVisibleCount = 0;
|
|
1356
|
+
this.invalidateSessionTimelineMeasurementCache();
|
|
1357
|
+
if (this.mainTab !== 'sessions' || !this.sessionPreviewRenderEnabled) {
|
|
1358
|
+
return;
|
|
1359
|
+
}
|
|
1360
|
+
const total = Array.isArray(this.activeSessionMessages)
|
|
1361
|
+
? this.activeSessionMessages.length
|
|
1362
|
+
: 0;
|
|
1363
|
+
if (total <= 0) return;
|
|
1364
|
+
const baseSize = Number.isFinite(this.sessionPreviewInitialBatchSize)
|
|
1365
|
+
? Math.max(1, Math.floor(this.sessionPreviewInitialBatchSize))
|
|
1366
|
+
: 40;
|
|
1367
|
+
this.sessionPreviewVisibleCount = Math.min(baseSize, total);
|
|
1368
|
+
this.invalidateSessionTimelineMeasurementCache();
|
|
1369
|
+
},
|
|
1370
|
+
|
|
1371
|
+
async loadMoreSessionMessages(stepSize) {
|
|
1372
|
+
return loadMoreSessionMessagesHelper.call(this, stepSize);
|
|
1373
|
+
},
|
|
1374
|
+
|
|
1375
|
+
suspendSessionTabRender() {
|
|
1376
|
+
this.sessionTabRenderTicket += 1;
|
|
1377
|
+
this.sessionListRenderEnabled = false;
|
|
1378
|
+
this.sessionPreviewRenderEnabled = false;
|
|
1379
|
+
this.cancelSessionTimelineSync();
|
|
1380
|
+
this.sessionTimelineActiveKey = '';
|
|
1381
|
+
this.sessionTimelineLastSyncAt = 0;
|
|
1382
|
+
this.sessionTimelineLastScrollTop = 0;
|
|
1383
|
+
this.sessionTimelineLastAnchorY = 0;
|
|
1384
|
+
this.sessionTimelineLastDirection = 0;
|
|
1385
|
+
this.sessionPreviewScrollEl = null;
|
|
1386
|
+
this.sessionPreviewContainerEl = null;
|
|
1387
|
+
this.sessionPreviewHeaderEl = null;
|
|
1388
|
+
},
|
|
1389
|
+
|
|
1390
|
+
finalizeSessionTabTeardown() {
|
|
1391
|
+
this.resetSessionPreviewMessageRender();
|
|
1392
|
+
this.sessionPreviewPendingVisibleCount = 0;
|
|
1393
|
+
this.clearSessionTimelineRefs();
|
|
1394
|
+
},
|
|
1395
|
+
|
|
1396
|
+
teardownSessionTabRender() {
|
|
1397
|
+
this.suspendSessionTabRender();
|
|
1398
|
+
this.finalizeSessionTabTeardown();
|
|
1399
|
+
},
|
|
1400
|
+
|
|
1401
|
+
prepareSessionTabRender() {
|
|
1402
|
+
const ticket = ++this.sessionTabRenderTicket;
|
|
1403
|
+
this.sessionListRenderEnabled = false;
|
|
1404
|
+
this.sessionPreviewRenderEnabled = false;
|
|
1405
|
+
this.resetSessionPreviewMessageRender();
|
|
1406
|
+
|
|
1407
|
+
this.scheduleAfterFrame(() => {
|
|
1408
|
+
if (ticket !== this.sessionTabRenderTicket || this.mainTab !== 'sessions') {
|
|
1409
|
+
return;
|
|
1410
|
+
}
|
|
1411
|
+
this.sessionListRenderEnabled = true;
|
|
1412
|
+
|
|
1413
|
+
this.scheduleAfterFrame(() => {
|
|
1414
|
+
if (ticket !== this.sessionTabRenderTicket || this.mainTab !== 'sessions') {
|
|
1415
|
+
return;
|
|
1416
|
+
}
|
|
1417
|
+
this.sessionPreviewRenderEnabled = true;
|
|
1418
|
+
this.$nextTick(() => {
|
|
1419
|
+
if (ticket !== this.sessionTabRenderTicket || this.mainTab !== 'sessions') {
|
|
1420
|
+
return;
|
|
1421
|
+
}
|
|
1422
|
+
this.primeSessionPreviewMessageRender();
|
|
1423
|
+
this.updateSessionTimelineOffset();
|
|
1424
|
+
this.scheduleSessionTimelineSync();
|
|
1425
|
+
});
|
|
1426
|
+
});
|
|
1427
|
+
});
|
|
745
1428
|
},
|
|
746
1429
|
|
|
747
1430
|
getSessionStandaloneContext() {
|
|
@@ -788,6 +1471,7 @@ import { createSkillsMethods } from './modules/skills.methods.mjs';
|
|
|
788
1471
|
|
|
789
1472
|
this.sessionStandalone = true;
|
|
790
1473
|
this.mainTab = 'sessions';
|
|
1474
|
+
this.prepareSessionTabRender();
|
|
791
1475
|
|
|
792
1476
|
if (context.error || !context.params) {
|
|
793
1477
|
this.sessionStandaloneError = `会话链接参数不完整:${context.error || '参数解析失败'}`;
|
|
@@ -807,7 +1491,7 @@ import { createSkillsMethods } from './modules/skills.methods.mjs';
|
|
|
807
1491
|
this.activeSessionDetailClipped = false;
|
|
808
1492
|
this.cancelSessionTimelineSync();
|
|
809
1493
|
this.sessionTimelineActiveKey = '';
|
|
810
|
-
this.
|
|
1494
|
+
this.clearSessionTimelineRefs();
|
|
811
1495
|
this.sessionStandaloneError = '';
|
|
812
1496
|
this.sessionStandaloneText = '';
|
|
813
1497
|
this.sessionStandaloneTitle = this.activeSession.title || '会话';
|
|
@@ -996,6 +1680,7 @@ import { createSkillsMethods } from './modules/skills.methods.mjs';
|
|
|
996
1680
|
const name = typeof payload.name === 'string' ? payload.name.trim() : '';
|
|
997
1681
|
const baseUrl = typeof payload.baseUrl === 'string' ? payload.baseUrl.trim() : '';
|
|
998
1682
|
const apiKey = typeof payload.apiKey === 'string' ? payload.apiKey : '';
|
|
1683
|
+
const model = typeof payload.model === 'string' ? payload.model.trim() : '';
|
|
999
1684
|
if (!name || !baseUrl) return '';
|
|
1000
1685
|
|
|
1001
1686
|
const nameArg = this.quoteShellArg(name);
|
|
@@ -1005,7 +1690,8 @@ import { createSkillsMethods } from './modules/skills.methods.mjs';
|
|
|
1005
1690
|
const addCmd = apiKey
|
|
1006
1691
|
? `codexmate add ${nameArg} ${urlArg} ${keyArg}`
|
|
1007
1692
|
: `codexmate add ${nameArg} ${urlArg}`;
|
|
1008
|
-
|
|
1693
|
+
const modelCmd = model ? ` && codexmate use ${this.quoteShellArg(model)}` : '';
|
|
1694
|
+
return `${addCmd} && ${switchCmd}${modelCmd}`;
|
|
1009
1695
|
},
|
|
1010
1696
|
|
|
1011
1697
|
buildClaudeShareCommand(payload) {
|
|
@@ -1157,7 +1843,7 @@ import { createSkillsMethods } from './modules/skills.methods.mjs';
|
|
|
1157
1843
|
}
|
|
1158
1844
|
this.sessionDeleting[key] = true;
|
|
1159
1845
|
try {
|
|
1160
|
-
const res = await api('
|
|
1846
|
+
const res = await api('trash-session', {
|
|
1161
1847
|
source: session.source,
|
|
1162
1848
|
sessionId: session.sessionId,
|
|
1163
1849
|
filePath: session.filePath
|
|
@@ -1166,8 +1852,29 @@ import { createSkillsMethods } from './modules/skills.methods.mjs';
|
|
|
1166
1852
|
this.showMessage(res.error, 'error');
|
|
1167
1853
|
return;
|
|
1168
1854
|
}
|
|
1169
|
-
this.
|
|
1170
|
-
|
|
1855
|
+
this.removeSessionPin(session);
|
|
1856
|
+
this.invalidateSessionTrashRequests();
|
|
1857
|
+
this.showMessage('已移入回收站', 'success');
|
|
1858
|
+
if (this.sessionTrashLoadedOnce) {
|
|
1859
|
+
this.prependSessionTrashItem(this.buildSessionTrashItemFromSession(session, res), {
|
|
1860
|
+
totalCount: res && res.totalCount !== undefined ? res.totalCount : undefined
|
|
1861
|
+
});
|
|
1862
|
+
} else if (this.sessionTrashCountLoadedOnce) {
|
|
1863
|
+
this.sessionTrashTotalCount = this.normalizeSessionTrashTotalCount(
|
|
1864
|
+
res && res.totalCount !== undefined
|
|
1865
|
+
? res.totalCount
|
|
1866
|
+
: (this.normalizeSessionTrashTotalCount(this.sessionTrashTotalCount, this.sessionTrashItems) + 1),
|
|
1867
|
+
this.sessionTrashItems
|
|
1868
|
+
);
|
|
1869
|
+
} else {
|
|
1870
|
+
this.sessionTrashTotalCount = this.normalizeSessionTrashTotalCount(
|
|
1871
|
+
res && res.totalCount !== undefined
|
|
1872
|
+
? res.totalCount
|
|
1873
|
+
: (this.normalizeSessionTrashTotalCount(this.sessionTrashTotalCount, this.sessionTrashItems) + 1),
|
|
1874
|
+
this.sessionTrashItems
|
|
1875
|
+
);
|
|
1876
|
+
}
|
|
1877
|
+
await this.removeSessionFromCurrentList(session);
|
|
1171
1878
|
} catch (e) {
|
|
1172
1879
|
this.showMessage('删除失败', 'error');
|
|
1173
1880
|
} finally {
|
|
@@ -1175,6 +1882,413 @@ import { createSkillsMethods } from './modules/skills.methods.mjs';
|
|
|
1175
1882
|
}
|
|
1176
1883
|
},
|
|
1177
1884
|
|
|
1885
|
+
buildSessionTrashItemFromSession(session, result = {}) {
|
|
1886
|
+
const deletedAt = typeof result.deletedAt === 'string' && result.deletedAt
|
|
1887
|
+
? result.deletedAt
|
|
1888
|
+
: new Date().toISOString();
|
|
1889
|
+
const source = session && session.source === 'claude' ? 'claude' : 'codex';
|
|
1890
|
+
return {
|
|
1891
|
+
trashId: typeof result.trashId === 'string' ? result.trashId : '',
|
|
1892
|
+
source,
|
|
1893
|
+
sourceLabel: session && typeof session.sourceLabel === 'string' && session.sourceLabel
|
|
1894
|
+
? session.sourceLabel
|
|
1895
|
+
: (source === 'claude' ? 'Claude Code' : 'Codex'),
|
|
1896
|
+
sessionId: session && typeof session.sessionId === 'string' ? session.sessionId : '',
|
|
1897
|
+
title: session && typeof session.title === 'string' && session.title
|
|
1898
|
+
? session.title
|
|
1899
|
+
: (session && typeof session.sessionId === 'string' ? session.sessionId : ''),
|
|
1900
|
+
cwd: session && typeof session.cwd === 'string' ? session.cwd : '',
|
|
1901
|
+
createdAt: session && typeof session.createdAt === 'string' ? session.createdAt : '',
|
|
1902
|
+
updatedAt: session && typeof session.updatedAt === 'string' ? session.updatedAt : '',
|
|
1903
|
+
deletedAt,
|
|
1904
|
+
messageCount: Number.isFinite(Number(result && result.messageCount))
|
|
1905
|
+
? Math.max(0, Math.floor(Number(result.messageCount)))
|
|
1906
|
+
: (Number.isFinite(Number(session && session.messageCount))
|
|
1907
|
+
? Math.max(0, Math.floor(Number(session.messageCount)))
|
|
1908
|
+
: 0),
|
|
1909
|
+
originalFilePath: session && typeof session.filePath === 'string' ? session.filePath : '',
|
|
1910
|
+
provider: session && typeof session.provider === 'string' ? session.provider : source,
|
|
1911
|
+
keywords: Array.isArray(session && session.keywords) ? session.keywords : [],
|
|
1912
|
+
capabilities: session && typeof session.capabilities === 'object' && session.capabilities
|
|
1913
|
+
? session.capabilities
|
|
1914
|
+
: {},
|
|
1915
|
+
claudeIndexPath: '',
|
|
1916
|
+
claudeIndexEntry: null,
|
|
1917
|
+
trashFilePath: ''
|
|
1918
|
+
};
|
|
1919
|
+
},
|
|
1920
|
+
|
|
1921
|
+
prependSessionTrashItem(item, options = {}) {
|
|
1922
|
+
if (!item || !item.trashId) {
|
|
1923
|
+
return;
|
|
1924
|
+
}
|
|
1925
|
+
const existing = Array.isArray(this.sessionTrashItems) ? this.sessionTrashItems : [];
|
|
1926
|
+
const filtered = existing.filter((entry) => this.getSessionTrashActionKey(entry) !== item.trashId);
|
|
1927
|
+
const nextItems = [item, ...filtered].slice(0, SESSION_TRASH_LIST_LIMIT);
|
|
1928
|
+
const previousTotalCount = Number(this.sessionTrashTotalCount);
|
|
1929
|
+
const normalizedPreviousTotal = Number.isFinite(previousTotalCount) && previousTotalCount >= 0
|
|
1930
|
+
? Math.max(existing.length, Math.floor(previousTotalCount))
|
|
1931
|
+
: existing.length;
|
|
1932
|
+
this.sessionTrashItems = nextItems;
|
|
1933
|
+
const previousVisibleCount = Number(this.sessionTrashVisibleCount);
|
|
1934
|
+
const normalizedPreviousVisibleCount = Number.isFinite(previousVisibleCount) && previousVisibleCount > 0
|
|
1935
|
+
? Math.floor(previousVisibleCount)
|
|
1936
|
+
: SESSION_TRASH_PAGE_SIZE;
|
|
1937
|
+
const wasFullyExpanded = normalizedPreviousVisibleCount >= existing.length
|
|
1938
|
+
|| normalizedPreviousVisibleCount >= normalizedPreviousTotal;
|
|
1939
|
+
if (wasFullyExpanded) {
|
|
1940
|
+
this.sessionTrashVisibleCount = Math.min(
|
|
1941
|
+
normalizedPreviousVisibleCount + 1,
|
|
1942
|
+
nextItems.length || (normalizedPreviousVisibleCount + 1)
|
|
1943
|
+
);
|
|
1944
|
+
}
|
|
1945
|
+
const fallbackTotalCount = filtered.length === existing.length
|
|
1946
|
+
? normalizedPreviousTotal + 1
|
|
1947
|
+
: normalizedPreviousTotal;
|
|
1948
|
+
this.sessionTrashTotalCount = this.normalizeSessionTrashTotalCount(
|
|
1949
|
+
options && options.totalCount !== undefined
|
|
1950
|
+
? options.totalCount
|
|
1951
|
+
: fallbackTotalCount,
|
|
1952
|
+
nextItems
|
|
1953
|
+
);
|
|
1954
|
+
},
|
|
1955
|
+
|
|
1956
|
+
normalizeSessionTrashTotalCount(totalCount, fallbackItems = this.sessionTrashItems) {
|
|
1957
|
+
const fallbackCount = Array.isArray(fallbackItems) ? fallbackItems.length : 0;
|
|
1958
|
+
const numericTotal = Number(totalCount);
|
|
1959
|
+
if (!Number.isFinite(numericTotal) || numericTotal < 0) {
|
|
1960
|
+
return fallbackCount;
|
|
1961
|
+
}
|
|
1962
|
+
return Math.floor(numericTotal);
|
|
1963
|
+
},
|
|
1964
|
+
|
|
1965
|
+
getSessionTrashViewState() {
|
|
1966
|
+
if (this.sessionTrashLoading && !this.sessionTrashLoadedOnce) {
|
|
1967
|
+
return 'loading';
|
|
1968
|
+
}
|
|
1969
|
+
const totalCount = Number(this.sessionTrashCount);
|
|
1970
|
+
const normalizedTotalCount = Number.isFinite(totalCount) && totalCount >= 0
|
|
1971
|
+
? Math.floor(totalCount)
|
|
1972
|
+
: 0;
|
|
1973
|
+
const hasVisibleItems = Array.isArray(this.sessionTrashItems) && this.sessionTrashItems.length > 0;
|
|
1974
|
+
if (this.sessionTrashLastLoadFailed && (!this.sessionTrashLoadedOnce || !hasVisibleItems)) {
|
|
1975
|
+
return 'retry';
|
|
1976
|
+
}
|
|
1977
|
+
if (!this.sessionTrashLoadedOnce) {
|
|
1978
|
+
return normalizedTotalCount > 0 ? 'retry' : 'empty';
|
|
1979
|
+
}
|
|
1980
|
+
if (normalizedTotalCount === 0) {
|
|
1981
|
+
return 'empty';
|
|
1982
|
+
}
|
|
1983
|
+
return hasVisibleItems ? 'list' : 'retry';
|
|
1984
|
+
},
|
|
1985
|
+
|
|
1986
|
+
issueSessionTrashCountRequestToken() {
|
|
1987
|
+
const currentToken = Number(this.sessionTrashCountRequestToken);
|
|
1988
|
+
const nextToken = Number.isFinite(currentToken) && currentToken >= 0
|
|
1989
|
+
? Math.floor(currentToken) + 1
|
|
1990
|
+
: 1;
|
|
1991
|
+
this.sessionTrashCountRequestToken = nextToken;
|
|
1992
|
+
return nextToken;
|
|
1993
|
+
},
|
|
1994
|
+
|
|
1995
|
+
issueSessionTrashListRequestToken() {
|
|
1996
|
+
const currentToken = Number(this.sessionTrashListRequestToken);
|
|
1997
|
+
const nextToken = Number.isFinite(currentToken) && currentToken >= 0
|
|
1998
|
+
? Math.floor(currentToken) + 1
|
|
1999
|
+
: 1;
|
|
2000
|
+
this.sessionTrashListRequestToken = nextToken;
|
|
2001
|
+
return nextToken;
|
|
2002
|
+
},
|
|
2003
|
+
|
|
2004
|
+
invalidateSessionTrashRequests() {
|
|
2005
|
+
this.issueSessionTrashCountRequestToken();
|
|
2006
|
+
return this.issueSessionTrashListRequestToken();
|
|
2007
|
+
},
|
|
2008
|
+
|
|
2009
|
+
isLatestSessionTrashCountRequestToken(token) {
|
|
2010
|
+
return Number(token) === Number(this.sessionTrashCountRequestToken);
|
|
2011
|
+
},
|
|
2012
|
+
|
|
2013
|
+
isLatestSessionTrashListRequestToken(token) {
|
|
2014
|
+
return Number(token) === Number(this.sessionTrashListRequestToken);
|
|
2015
|
+
},
|
|
2016
|
+
|
|
2017
|
+
resetSessionTrashVisibleCount() {
|
|
2018
|
+
const totalItems = Array.isArray(this.sessionTrashItems) ? this.sessionTrashItems.length : 0;
|
|
2019
|
+
this.sessionTrashVisibleCount = Math.min(totalItems, SESSION_TRASH_PAGE_SIZE) || SESSION_TRASH_PAGE_SIZE;
|
|
2020
|
+
},
|
|
2021
|
+
|
|
2022
|
+
loadMoreSessionTrashItems() {
|
|
2023
|
+
const totalItems = Array.isArray(this.sessionTrashItems) ? this.sessionTrashItems.length : 0;
|
|
2024
|
+
const visibleCount = Number(this.sessionTrashVisibleCount);
|
|
2025
|
+
const safeVisibleCount = Number.isFinite(visibleCount) && visibleCount > 0
|
|
2026
|
+
? Math.floor(visibleCount)
|
|
2027
|
+
: SESSION_TRASH_PAGE_SIZE;
|
|
2028
|
+
this.sessionTrashVisibleCount = Math.min(totalItems, safeVisibleCount + SESSION_TRASH_PAGE_SIZE);
|
|
2029
|
+
},
|
|
2030
|
+
|
|
2031
|
+
clearActiveSessionState() {
|
|
2032
|
+
this.activeSession = null;
|
|
2033
|
+
this.activeSessionMessages = [];
|
|
2034
|
+
this.resetSessionDetailPagination();
|
|
2035
|
+
this.resetSessionPreviewMessageRender();
|
|
2036
|
+
this.activeSessionDetailError = '';
|
|
2037
|
+
this.activeSessionDetailClipped = false;
|
|
2038
|
+
this.cancelSessionTimelineSync();
|
|
2039
|
+
this.sessionTimelineActiveKey = '';
|
|
2040
|
+
this.clearSessionTimelineRefs();
|
|
2041
|
+
},
|
|
2042
|
+
|
|
2043
|
+
async removeSessionFromCurrentList(session) {
|
|
2044
|
+
const sessionKey = this.getSessionExportKey(session);
|
|
2045
|
+
if (!sessionKey) {
|
|
2046
|
+
return;
|
|
2047
|
+
}
|
|
2048
|
+
const currentList = Array.isArray(this.sessionsList) ? [...this.sessionsList] : [];
|
|
2049
|
+
const removedIndex = currentList.findIndex((item) => this.getSessionExportKey(item) === sessionKey);
|
|
2050
|
+
if (removedIndex < 0) {
|
|
2051
|
+
return;
|
|
2052
|
+
}
|
|
2053
|
+
const activeKey = this.activeSession ? this.getSessionExportKey(this.activeSession) : '';
|
|
2054
|
+
const renderedList = Array.isArray(this.sortedSessionsList) ? this.sortedSessionsList : [];
|
|
2055
|
+
const renderedIndex = renderedList.findIndex((item) => this.getSessionExportKey(item) === sessionKey);
|
|
2056
|
+
let nextActiveKey = '';
|
|
2057
|
+
if (activeKey === sessionKey && renderedIndex >= 0) {
|
|
2058
|
+
const fallbackSession = renderedList[renderedIndex - 1] || renderedList[renderedIndex + 1] || null;
|
|
2059
|
+
nextActiveKey = fallbackSession ? this.getSessionExportKey(fallbackSession) : '';
|
|
2060
|
+
}
|
|
2061
|
+
currentList.splice(removedIndex, 1);
|
|
2062
|
+
this.sessionsList = currentList;
|
|
2063
|
+
this.syncSessionPathOptionsForSource(
|
|
2064
|
+
this.sessionFilterSource,
|
|
2065
|
+
this.extractPathOptionsFromSessions(currentList),
|
|
2066
|
+
false
|
|
2067
|
+
);
|
|
2068
|
+
if (activeKey !== sessionKey) {
|
|
2069
|
+
return;
|
|
2070
|
+
}
|
|
2071
|
+
if (currentList.length === 0) {
|
|
2072
|
+
this.clearActiveSessionState();
|
|
2073
|
+
return;
|
|
2074
|
+
}
|
|
2075
|
+
const nextSession = currentList.find((item) => this.getSessionExportKey(item) === nextActiveKey)
|
|
2076
|
+
|| currentList[Math.min(removedIndex, currentList.length - 1)];
|
|
2077
|
+
if (!nextSession) {
|
|
2078
|
+
this.clearActiveSessionState();
|
|
2079
|
+
return;
|
|
2080
|
+
}
|
|
2081
|
+
await this.selectSession(nextSession);
|
|
2082
|
+
},
|
|
2083
|
+
|
|
2084
|
+
normalizeSettingsTab(tab) {
|
|
2085
|
+
return tab === 'trash' ? 'trash' : 'backup';
|
|
2086
|
+
},
|
|
2087
|
+
|
|
2088
|
+
async onSettingsTabClick(tab) {
|
|
2089
|
+
await this.switchSettingsTab(tab);
|
|
2090
|
+
},
|
|
2091
|
+
|
|
2092
|
+
async switchSettingsTab(tab, options = {}) {
|
|
2093
|
+
const nextTab = this.normalizeSettingsTab(tab);
|
|
2094
|
+
this.settingsTab = nextTab;
|
|
2095
|
+
if (nextTab !== 'trash') {
|
|
2096
|
+
return;
|
|
2097
|
+
}
|
|
2098
|
+
const forceRefresh = options.forceRefresh === true;
|
|
2099
|
+
if (forceRefresh || !this.sessionTrashLoadedOnce) {
|
|
2100
|
+
await this.loadSessionTrash({ forceRefresh });
|
|
2101
|
+
}
|
|
2102
|
+
},
|
|
2103
|
+
|
|
2104
|
+
async loadSessionTrashCount(options = {}) {
|
|
2105
|
+
if (this.sessionTrashCountLoading) {
|
|
2106
|
+
this.sessionTrashCountPendingOptions = {
|
|
2107
|
+
...(this.sessionTrashCountPendingOptions || {}),
|
|
2108
|
+
...(options || {})
|
|
2109
|
+
};
|
|
2110
|
+
return;
|
|
2111
|
+
}
|
|
2112
|
+
const requestToken = this.issueSessionTrashCountRequestToken();
|
|
2113
|
+
this.sessionTrashCountLoading = true;
|
|
2114
|
+
try {
|
|
2115
|
+
const res = await api('list-session-trash', { countOnly: true });
|
|
2116
|
+
if (!this.isLatestSessionTrashCountRequestToken(requestToken)) {
|
|
2117
|
+
return;
|
|
2118
|
+
}
|
|
2119
|
+
if (res.error) {
|
|
2120
|
+
if (options.silent !== true) {
|
|
2121
|
+
this.showMessage(res.error, 'error');
|
|
2122
|
+
}
|
|
2123
|
+
return;
|
|
2124
|
+
}
|
|
2125
|
+
this.sessionTrashTotalCount = this.normalizeSessionTrashTotalCount(
|
|
2126
|
+
res.totalCount,
|
|
2127
|
+
this.sessionTrashItems
|
|
2128
|
+
);
|
|
2129
|
+
this.sessionTrashCountLoadedOnce = true;
|
|
2130
|
+
} catch (e) {
|
|
2131
|
+
if (this.isLatestSessionTrashCountRequestToken(requestToken) && options.silent !== true) {
|
|
2132
|
+
this.showMessage('加载回收站数量失败', 'error');
|
|
2133
|
+
}
|
|
2134
|
+
} finally {
|
|
2135
|
+
this.sessionTrashCountLoading = false;
|
|
2136
|
+
const pendingOptions = this.sessionTrashCountPendingOptions;
|
|
2137
|
+
this.sessionTrashCountPendingOptions = null;
|
|
2138
|
+
if (pendingOptions) {
|
|
2139
|
+
await this.loadSessionTrashCount(pendingOptions);
|
|
2140
|
+
}
|
|
2141
|
+
}
|
|
2142
|
+
},
|
|
2143
|
+
|
|
2144
|
+
getSessionTrashActionKey(item) {
|
|
2145
|
+
return item && typeof item.trashId === 'string' ? item.trashId : '';
|
|
2146
|
+
},
|
|
2147
|
+
|
|
2148
|
+
isSessionTrashActionBusy(item) {
|
|
2149
|
+
const key = typeof item === 'string' ? item : this.getSessionTrashActionKey(item);
|
|
2150
|
+
return !!(key && (this.sessionTrashRestoring[key] || this.sessionTrashPurging[key]));
|
|
2151
|
+
},
|
|
2152
|
+
|
|
2153
|
+
async loadSessionTrash(options = {}) {
|
|
2154
|
+
if (this.sessionTrashLoading) {
|
|
2155
|
+
this.sessionTrashPendingOptions = {
|
|
2156
|
+
...(this.sessionTrashPendingOptions || {}),
|
|
2157
|
+
...(options || {})
|
|
2158
|
+
};
|
|
2159
|
+
return;
|
|
2160
|
+
}
|
|
2161
|
+
const requestToken = this.issueSessionTrashListRequestToken();
|
|
2162
|
+
this.sessionTrashLoading = true;
|
|
2163
|
+
this.sessionTrashLastLoadFailed = false;
|
|
2164
|
+
let loadSucceeded = false;
|
|
2165
|
+
try {
|
|
2166
|
+
const res = await api('list-session-trash', {
|
|
2167
|
+
limit: SESSION_TRASH_LIST_LIMIT,
|
|
2168
|
+
forceRefresh: !!options.forceRefresh
|
|
2169
|
+
});
|
|
2170
|
+
if (!this.isLatestSessionTrashListRequestToken(requestToken)) {
|
|
2171
|
+
return;
|
|
2172
|
+
}
|
|
2173
|
+
if (res.error) {
|
|
2174
|
+
this.sessionTrashLastLoadFailed = true;
|
|
2175
|
+
this.showMessage(res.error, 'error');
|
|
2176
|
+
return;
|
|
2177
|
+
}
|
|
2178
|
+
const nextItems = Array.isArray(res.items) ? res.items : [];
|
|
2179
|
+
this.sessionTrashItems = nextItems;
|
|
2180
|
+
this.resetSessionTrashVisibleCount();
|
|
2181
|
+
this.sessionTrashTotalCount = this.normalizeSessionTrashTotalCount(res.totalCount, nextItems);
|
|
2182
|
+
this.sessionTrashCountLoadedOnce = true;
|
|
2183
|
+
this.sessionTrashLastLoadFailed = false;
|
|
2184
|
+
loadSucceeded = true;
|
|
2185
|
+
} catch (e) {
|
|
2186
|
+
if (this.isLatestSessionTrashListRequestToken(requestToken)) {
|
|
2187
|
+
this.sessionTrashLastLoadFailed = true;
|
|
2188
|
+
this.showMessage('加载回收站失败', 'error');
|
|
2189
|
+
}
|
|
2190
|
+
} finally {
|
|
2191
|
+
this.sessionTrashLoading = false;
|
|
2192
|
+
if (loadSucceeded) {
|
|
2193
|
+
this.sessionTrashLoadedOnce = true;
|
|
2194
|
+
}
|
|
2195
|
+
const pendingOptions = this.sessionTrashPendingOptions;
|
|
2196
|
+
this.sessionTrashPendingOptions = null;
|
|
2197
|
+
if (pendingOptions) {
|
|
2198
|
+
await this.loadSessionTrash(pendingOptions);
|
|
2199
|
+
}
|
|
2200
|
+
}
|
|
2201
|
+
},
|
|
2202
|
+
|
|
2203
|
+
async restoreSessionTrash(item) {
|
|
2204
|
+
const key = this.getSessionTrashActionKey(item);
|
|
2205
|
+
if (!key || this.isSessionTrashActionBusy(key) || this.sessionTrashClearing) {
|
|
2206
|
+
return;
|
|
2207
|
+
}
|
|
2208
|
+
this.sessionTrashRestoring[key] = true;
|
|
2209
|
+
try {
|
|
2210
|
+
const res = await api('restore-session-trash', { trashId: key });
|
|
2211
|
+
if (res.error) {
|
|
2212
|
+
this.showMessage(res.error, 'error');
|
|
2213
|
+
return;
|
|
2214
|
+
}
|
|
2215
|
+
this.showMessage('会话已恢复', 'success');
|
|
2216
|
+
this.invalidateSessionTrashRequests();
|
|
2217
|
+
await this.loadSessionTrash({ forceRefresh: true });
|
|
2218
|
+
if (this.sessionsLoadedOnce) {
|
|
2219
|
+
await this.loadSessions();
|
|
2220
|
+
}
|
|
2221
|
+
} catch (e) {
|
|
2222
|
+
this.showMessage('恢复失败', 'error');
|
|
2223
|
+
} finally {
|
|
2224
|
+
this.sessionTrashRestoring[key] = false;
|
|
2225
|
+
}
|
|
2226
|
+
},
|
|
2227
|
+
|
|
2228
|
+
async purgeSessionTrash(item) {
|
|
2229
|
+
const key = this.getSessionTrashActionKey(item);
|
|
2230
|
+
if (!key || this.isSessionTrashActionBusy(key) || this.sessionTrashClearing) {
|
|
2231
|
+
return;
|
|
2232
|
+
}
|
|
2233
|
+
const confirmed = await this.requestConfirmDialog({
|
|
2234
|
+
title: '彻底删除回收站记录',
|
|
2235
|
+
message: '该会话将从回收站永久删除,且无法恢复。',
|
|
2236
|
+
confirmText: '彻底删除',
|
|
2237
|
+
cancelText: '取消',
|
|
2238
|
+
danger: true
|
|
2239
|
+
});
|
|
2240
|
+
if (!confirmed) {
|
|
2241
|
+
return;
|
|
2242
|
+
}
|
|
2243
|
+
this.sessionTrashPurging[key] = true;
|
|
2244
|
+
try {
|
|
2245
|
+
const res = await api('purge-session-trash', { trashId: key });
|
|
2246
|
+
if (res.error) {
|
|
2247
|
+
this.showMessage(res.error, 'error');
|
|
2248
|
+
return;
|
|
2249
|
+
}
|
|
2250
|
+
this.showMessage('已彻底删除', 'success');
|
|
2251
|
+
this.invalidateSessionTrashRequests();
|
|
2252
|
+
await this.loadSessionTrash({ forceRefresh: true });
|
|
2253
|
+
} catch (e) {
|
|
2254
|
+
this.showMessage('彻底删除失败', 'error');
|
|
2255
|
+
} finally {
|
|
2256
|
+
this.sessionTrashPurging[key] = false;
|
|
2257
|
+
}
|
|
2258
|
+
},
|
|
2259
|
+
|
|
2260
|
+
async clearSessionTrash() {
|
|
2261
|
+
const normalizedCount = Number(this.sessionTrashCount);
|
|
2262
|
+
if (this.sessionTrashClearing || !Number.isFinite(normalizedCount) || normalizedCount <= 0) {
|
|
2263
|
+
return;
|
|
2264
|
+
}
|
|
2265
|
+
const confirmed = await this.requestConfirmDialog({
|
|
2266
|
+
title: '清空回收站',
|
|
2267
|
+
message: '该操作会永久删除回收站中的全部会话,且无法恢复。',
|
|
2268
|
+
confirmText: '全部清空',
|
|
2269
|
+
cancelText: '取消',
|
|
2270
|
+
danger: true
|
|
2271
|
+
});
|
|
2272
|
+
if (!confirmed) {
|
|
2273
|
+
return;
|
|
2274
|
+
}
|
|
2275
|
+
this.sessionTrashClearing = true;
|
|
2276
|
+
try {
|
|
2277
|
+
const res = await api('purge-session-trash', { all: true });
|
|
2278
|
+
if (res.error) {
|
|
2279
|
+
this.showMessage(res.error, 'error');
|
|
2280
|
+
return;
|
|
2281
|
+
}
|
|
2282
|
+
this.showMessage('回收站已清空', 'success');
|
|
2283
|
+
this.invalidateSessionTrashRequests();
|
|
2284
|
+
await this.loadSessionTrash({ forceRefresh: true });
|
|
2285
|
+
} catch (e) {
|
|
2286
|
+
this.showMessage('清空回收站失败', 'error');
|
|
2287
|
+
} finally {
|
|
2288
|
+
this.sessionTrashClearing = false;
|
|
2289
|
+
}
|
|
2290
|
+
},
|
|
2291
|
+
|
|
1178
2292
|
normalizeSessionPathValue(value) {
|
|
1179
2293
|
return normalizeSessionPathFilter(value);
|
|
1180
2294
|
},
|
|
@@ -1285,22 +2399,140 @@ import { createSkillsMethods } from './modules/skills.methods.mjs';
|
|
|
1285
2399
|
const value = this.sessionResumeWithYolo ? '1' : '0';
|
|
1286
2400
|
localStorage.setItem('codexmateSessionResumeYolo', value);
|
|
1287
2401
|
},
|
|
1288
|
-
restoreSessionFilterCache() {
|
|
1289
|
-
const sourceCache = localStorage.getItem('codexmateSessionFilterSource');
|
|
1290
|
-
const pathCache = localStorage.getItem('codexmateSessionPathFilter');
|
|
1291
|
-
const cached = buildSessionFilterCacheState(sourceCache, pathCache);
|
|
1292
|
-
this.sessionFilterSource = cached.source;
|
|
1293
|
-
this.sessionPathFilter = cached.pathFilter;
|
|
1294
|
-
this.refreshSessionPathOptions(this.sessionFilterSource);
|
|
2402
|
+
restoreSessionFilterCache() {
|
|
2403
|
+
const sourceCache = localStorage.getItem('codexmateSessionFilterSource');
|
|
2404
|
+
const pathCache = localStorage.getItem('codexmateSessionPathFilter');
|
|
2405
|
+
const cached = buildSessionFilterCacheState(sourceCache, pathCache);
|
|
2406
|
+
this.sessionFilterSource = cached.source;
|
|
2407
|
+
this.sessionPathFilter = cached.pathFilter;
|
|
2408
|
+
this.refreshSessionPathOptions(this.sessionFilterSource);
|
|
2409
|
+
},
|
|
2410
|
+
persistSessionFilterCache() {
|
|
2411
|
+
const cached = buildSessionFilterCacheState(this.sessionFilterSource, this.sessionPathFilter);
|
|
2412
|
+
localStorage.setItem('codexmateSessionFilterSource', cached.source);
|
|
2413
|
+
if (cached.pathFilter) {
|
|
2414
|
+
localStorage.setItem('codexmateSessionPathFilter', cached.pathFilter);
|
|
2415
|
+
} else {
|
|
2416
|
+
localStorage.removeItem('codexmateSessionPathFilter');
|
|
2417
|
+
}
|
|
2418
|
+
},
|
|
2419
|
+
normalizeSessionPinnedMap(raw) {
|
|
2420
|
+
if (!raw || typeof raw !== 'object' || Array.isArray(raw)) {
|
|
2421
|
+
return {};
|
|
2422
|
+
}
|
|
2423
|
+
const next = {};
|
|
2424
|
+
for (const [key, value] of Object.entries(raw)) {
|
|
2425
|
+
if (!key) continue;
|
|
2426
|
+
const numeric = Number(value);
|
|
2427
|
+
if (!Number.isFinite(numeric) || numeric <= 0) continue;
|
|
2428
|
+
next[key] = Math.floor(numeric);
|
|
2429
|
+
}
|
|
2430
|
+
return next;
|
|
2431
|
+
},
|
|
2432
|
+
restoreSessionPinnedMap() {
|
|
2433
|
+
const cached = localStorage.getItem('codexmateSessionPinnedMap');
|
|
2434
|
+
if (!cached) {
|
|
2435
|
+
this.sessionPinnedMap = {};
|
|
2436
|
+
return;
|
|
2437
|
+
}
|
|
2438
|
+
try {
|
|
2439
|
+
const parsed = JSON.parse(cached);
|
|
2440
|
+
this.sessionPinnedMap = this.normalizeSessionPinnedMap(parsed);
|
|
2441
|
+
} catch (_) {
|
|
2442
|
+
this.sessionPinnedMap = {};
|
|
2443
|
+
localStorage.removeItem('codexmateSessionPinnedMap');
|
|
2444
|
+
}
|
|
2445
|
+
},
|
|
2446
|
+
persistSessionPinnedMap() {
|
|
2447
|
+
const payload = (this.sessionPinnedMap && typeof this.sessionPinnedMap === 'object')
|
|
2448
|
+
? this.sessionPinnedMap
|
|
2449
|
+
: {};
|
|
2450
|
+
localStorage.setItem('codexmateSessionPinnedMap', JSON.stringify(payload));
|
|
2451
|
+
},
|
|
2452
|
+
shouldPruneSessionPinnedMap(sessions = this.sessionsList) {
|
|
2453
|
+
if (!Array.isArray(sessions) || sessions.length === 0) {
|
|
2454
|
+
return false;
|
|
2455
|
+
}
|
|
2456
|
+
if (this.sessionFilterSource !== 'all') {
|
|
2457
|
+
return false;
|
|
2458
|
+
}
|
|
2459
|
+
if (this.sessionPathFilter) {
|
|
2460
|
+
return false;
|
|
2461
|
+
}
|
|
2462
|
+
if (this.sessionQuery && isSessionQueryEnabled(this.sessionFilterSource)) {
|
|
2463
|
+
return false;
|
|
2464
|
+
}
|
|
2465
|
+
if (this.sessionRoleFilter && this.sessionRoleFilter !== 'all') {
|
|
2466
|
+
return false;
|
|
2467
|
+
}
|
|
2468
|
+
if (this.sessionTimePreset && this.sessionTimePreset !== 'all') {
|
|
2469
|
+
return false;
|
|
2470
|
+
}
|
|
2471
|
+
return true;
|
|
2472
|
+
},
|
|
2473
|
+
pruneSessionPinnedMap(sessions = this.sessionsList) {
|
|
2474
|
+
const current = (this.sessionPinnedMap && typeof this.sessionPinnedMap === 'object')
|
|
2475
|
+
? this.sessionPinnedMap
|
|
2476
|
+
: {};
|
|
2477
|
+
const list = Array.isArray(sessions) ? sessions : [];
|
|
2478
|
+
if (Object.keys(current).length === 0 || !this.shouldPruneSessionPinnedMap(list)) {
|
|
2479
|
+
return;
|
|
2480
|
+
}
|
|
2481
|
+
const validKeys = new Set(list.map((session) => this.getSessionExportKey(session)).filter(Boolean));
|
|
2482
|
+
const next = {};
|
|
2483
|
+
let changed = false;
|
|
2484
|
+
for (const [key, value] of Object.entries(current)) {
|
|
2485
|
+
if (!validKeys.has(key)) {
|
|
2486
|
+
changed = true;
|
|
2487
|
+
continue;
|
|
2488
|
+
}
|
|
2489
|
+
next[key] = value;
|
|
2490
|
+
}
|
|
2491
|
+
if (!changed) {
|
|
2492
|
+
return;
|
|
2493
|
+
}
|
|
2494
|
+
this.sessionPinnedMap = next;
|
|
2495
|
+
this.persistSessionPinnedMap();
|
|
2496
|
+
},
|
|
2497
|
+
getSessionPinTimestamp(session) {
|
|
2498
|
+
if (!session) return 0;
|
|
2499
|
+
const key = this.getSessionExportKey(session);
|
|
2500
|
+
if (!key) return 0;
|
|
2501
|
+
const raw = this.sessionPinnedMap && this.sessionPinnedMap[key];
|
|
2502
|
+
const numeric = Number(raw);
|
|
2503
|
+
return Number.isFinite(numeric) && numeric > 0 ? Math.floor(numeric) : 0;
|
|
1295
2504
|
},
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
2505
|
+
isSessionPinned(session) {
|
|
2506
|
+
return this.getSessionPinTimestamp(session) > 0;
|
|
2507
|
+
},
|
|
2508
|
+
toggleSessionPin(session) {
|
|
2509
|
+
if (!session) return;
|
|
2510
|
+
const key = this.getSessionExportKey(session);
|
|
2511
|
+
if (!key) return;
|
|
2512
|
+
const current = (this.sessionPinnedMap && typeof this.sessionPinnedMap === 'object')
|
|
2513
|
+
? this.sessionPinnedMap
|
|
2514
|
+
: {};
|
|
2515
|
+
const next = { ...current };
|
|
2516
|
+
if (next[key]) {
|
|
2517
|
+
delete next[key];
|
|
1301
2518
|
} else {
|
|
1302
|
-
|
|
2519
|
+
next[key] = Date.now();
|
|
1303
2520
|
}
|
|
2521
|
+
this.sessionPinnedMap = next;
|
|
2522
|
+
this.persistSessionPinnedMap();
|
|
2523
|
+
},
|
|
2524
|
+
removeSessionPin(session) {
|
|
2525
|
+
if (!session) return;
|
|
2526
|
+
const key = this.getSessionExportKey(session);
|
|
2527
|
+
if (!key) return;
|
|
2528
|
+
const current = (this.sessionPinnedMap && typeof this.sessionPinnedMap === 'object')
|
|
2529
|
+
? this.sessionPinnedMap
|
|
2530
|
+
: {};
|
|
2531
|
+
if (!current[key]) return;
|
|
2532
|
+
const next = { ...current };
|
|
2533
|
+
delete next[key];
|
|
2534
|
+
this.sessionPinnedMap = next;
|
|
2535
|
+
this.persistSessionPinnedMap();
|
|
1304
2536
|
},
|
|
1305
2537
|
|
|
1306
2538
|
async onSessionSourceChange() {
|
|
@@ -1352,13 +2584,137 @@ import { createSkillsMethods } from './modules/skills.methods.mjs';
|
|
|
1352
2584
|
},
|
|
1353
2585
|
setSessionPreviewScrollRef(el) {
|
|
1354
2586
|
this.sessionPreviewScrollEl = el || null;
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
2587
|
+
this.invalidateSessionTimelineMeasurementCache();
|
|
2588
|
+
const shouldSync = !!(
|
|
2589
|
+
this.sessionPreviewScrollEl
|
|
2590
|
+
&& this.mainTab === 'sessions'
|
|
2591
|
+
&& this.getMainTabForNav() === 'sessions'
|
|
2592
|
+
&& this.sessionPreviewRenderEnabled
|
|
2593
|
+
&& this.sessionTimelineNodes.length
|
|
2594
|
+
);
|
|
2595
|
+
if (!shouldSync) {
|
|
1358
2596
|
this.cancelSessionTimelineSync();
|
|
2597
|
+
this.updateSessionTimelineOffset();
|
|
2598
|
+
return;
|
|
1359
2599
|
}
|
|
2600
|
+
const boundScrollEl = this.sessionPreviewScrollEl;
|
|
2601
|
+
this.$nextTick(() => {
|
|
2602
|
+
if (this.sessionPreviewScrollEl !== boundScrollEl) return;
|
|
2603
|
+
if (this.mainTab !== 'sessions' || !this.sessionPreviewRenderEnabled) return;
|
|
2604
|
+
if (!this.sessionTimelineNodes.length) return;
|
|
2605
|
+
this.scheduleSessionTimelineSync();
|
|
2606
|
+
});
|
|
1360
2607
|
this.updateSessionTimelineOffset();
|
|
1361
2608
|
},
|
|
2609
|
+
clearSessionTimelineRefs() {
|
|
2610
|
+
this.sessionMessageRefMap = Object.create(null);
|
|
2611
|
+
this.sessionMessageRefBinderMap = Object.create(null);
|
|
2612
|
+
this.sessionTimelineLastAnchorY = 0;
|
|
2613
|
+
this.sessionTimelineLastDirection = 0;
|
|
2614
|
+
this.invalidateSessionTimelineMeasurementCache(true);
|
|
2615
|
+
},
|
|
2616
|
+
ensureSessionTimelineMeasurementCache() {
|
|
2617
|
+
if (this.__sessionTimelineMeasurementCache) {
|
|
2618
|
+
return this.__sessionTimelineMeasurementCache;
|
|
2619
|
+
}
|
|
2620
|
+
this.__sessionTimelineMeasurementCache = {
|
|
2621
|
+
offsetByKey: Object.create(null),
|
|
2622
|
+
dirty: true
|
|
2623
|
+
};
|
|
2624
|
+
return this.__sessionTimelineMeasurementCache;
|
|
2625
|
+
},
|
|
2626
|
+
invalidateSessionTimelineMeasurementCache(resetOffset = false) {
|
|
2627
|
+
const cache = this.ensureSessionTimelineMeasurementCache();
|
|
2628
|
+
if (resetOffset) {
|
|
2629
|
+
cache.offsetByKey = Object.create(null);
|
|
2630
|
+
}
|
|
2631
|
+
cache.dirty = true;
|
|
2632
|
+
},
|
|
2633
|
+
refreshSessionTimelineMeasurementCache(nodes = null) {
|
|
2634
|
+
const cache = this.ensureSessionTimelineMeasurementCache();
|
|
2635
|
+
const nodeList = Array.isArray(nodes) ? nodes : (Array.isArray(this.sessionTimelineNodes) ? this.sessionTimelineNodes : []);
|
|
2636
|
+
if (!nodeList.length) {
|
|
2637
|
+
cache.offsetByKey = Object.create(null);
|
|
2638
|
+
cache.dirty = false;
|
|
2639
|
+
return cache.offsetByKey;
|
|
2640
|
+
}
|
|
2641
|
+
const scrollEl = this.sessionPreviewScrollEl || this.$refs.sessionPreviewScroll;
|
|
2642
|
+
const scrollRect = scrollEl && typeof scrollEl.getBoundingClientRect === 'function'
|
|
2643
|
+
? scrollEl.getBoundingClientRect()
|
|
2644
|
+
: null;
|
|
2645
|
+
const scrollTop = scrollEl ? Number(scrollEl.scrollTop || 0) : 0;
|
|
2646
|
+
const nextOffsetByKey = Object.create(null);
|
|
2647
|
+
for (const node of nodeList) {
|
|
2648
|
+
if (!node || !node.key) continue;
|
|
2649
|
+
const messageEl = this.sessionMessageRefMap[node.key];
|
|
2650
|
+
if (!messageEl) continue;
|
|
2651
|
+
let top = Number.NaN;
|
|
2652
|
+
if (
|
|
2653
|
+
scrollRect
|
|
2654
|
+
&& typeof messageEl.getBoundingClientRect === 'function'
|
|
2655
|
+
) {
|
|
2656
|
+
const messageRect = messageEl.getBoundingClientRect();
|
|
2657
|
+
top = scrollTop + (messageRect.top - scrollRect.top);
|
|
2658
|
+
} else {
|
|
2659
|
+
top = Number(messageEl.offsetTop || 0);
|
|
2660
|
+
}
|
|
2661
|
+
if (!Number.isFinite(top)) continue;
|
|
2662
|
+
nextOffsetByKey[node.key] = top;
|
|
2663
|
+
}
|
|
2664
|
+
cache.offsetByKey = nextOffsetByKey;
|
|
2665
|
+
cache.dirty = false;
|
|
2666
|
+
return cache.offsetByKey;
|
|
2667
|
+
},
|
|
2668
|
+
getCachedSessionTimelineMeasuredNodes(nodes) {
|
|
2669
|
+
const nodeList = Array.isArray(nodes) ? nodes : [];
|
|
2670
|
+
if (!nodeList.length) {
|
|
2671
|
+
return [];
|
|
2672
|
+
}
|
|
2673
|
+
const cache = this.ensureSessionTimelineMeasurementCache();
|
|
2674
|
+
if (cache.dirty) {
|
|
2675
|
+
this.refreshSessionTimelineMeasurementCache(nodeList);
|
|
2676
|
+
}
|
|
2677
|
+
const offsetByKey = cache.offsetByKey || Object.create(null);
|
|
2678
|
+
const measuredNodes = [];
|
|
2679
|
+
for (const node of nodeList) {
|
|
2680
|
+
if (!node || !node.key) continue;
|
|
2681
|
+
const top = Number(offsetByKey[node.key]);
|
|
2682
|
+
if (!Number.isFinite(top)) continue;
|
|
2683
|
+
measuredNodes.push({
|
|
2684
|
+
key: node.key,
|
|
2685
|
+
top
|
|
2686
|
+
});
|
|
2687
|
+
}
|
|
2688
|
+
if (measuredNodes.length >= nodeList.length) {
|
|
2689
|
+
return measuredNodes;
|
|
2690
|
+
}
|
|
2691
|
+
const refreshedOffsetByKey = this.refreshSessionTimelineMeasurementCache(nodeList);
|
|
2692
|
+
const refreshedNodes = [];
|
|
2693
|
+
for (const node of nodeList) {
|
|
2694
|
+
if (!node || !node.key) continue;
|
|
2695
|
+
const top = Number(refreshedOffsetByKey[node.key]);
|
|
2696
|
+
if (!Number.isFinite(top)) continue;
|
|
2697
|
+
refreshedNodes.push({
|
|
2698
|
+
key: node.key,
|
|
2699
|
+
top
|
|
2700
|
+
});
|
|
2701
|
+
}
|
|
2702
|
+
return refreshedNodes;
|
|
2703
|
+
},
|
|
2704
|
+
getSessionMessageRefBinder(messageKey) {
|
|
2705
|
+
if (!this.isSessionTimelineNodeKey(messageKey)) return null;
|
|
2706
|
+
const current = this.sessionMessageRefBinderMap[messageKey];
|
|
2707
|
+
if (!current || current.ticket !== this.sessionTabRenderTicket) {
|
|
2708
|
+
const ticket = this.sessionTabRenderTicket;
|
|
2709
|
+
this.sessionMessageRefBinderMap[messageKey] = {
|
|
2710
|
+
ticket,
|
|
2711
|
+
bind: (el) => {
|
|
2712
|
+
this.bindSessionMessageRef(messageKey, el, ticket);
|
|
2713
|
+
}
|
|
2714
|
+
};
|
|
2715
|
+
}
|
|
2716
|
+
return this.sessionMessageRefBinderMap[messageKey].bind;
|
|
2717
|
+
},
|
|
1362
2718
|
updateSessionTimelineOffset() {
|
|
1363
2719
|
const container = this.sessionPreviewContainerEl || this.$refs.sessionPreviewContainer;
|
|
1364
2720
|
if (!container || !container.style) return;
|
|
@@ -1369,12 +2725,39 @@ import { createSkillsMethods } from './modules/skills.methods.mjs';
|
|
|
1369
2725
|
const offset = headerHeight > 0 ? (headerHeight + 12) : 72;
|
|
1370
2726
|
container.style.setProperty('--session-preview-header-offset', `${offset}px`);
|
|
1371
2727
|
},
|
|
1372
|
-
bindSessionMessageRef(messageKey, el) {
|
|
2728
|
+
bindSessionMessageRef(messageKey, el, ticket = this.sessionTabRenderTicket) {
|
|
2729
|
+
if (!this.sessionTimelineEnabled) return;
|
|
1373
2730
|
if (!messageKey) return;
|
|
2731
|
+
if (ticket !== this.sessionTabRenderTicket) return;
|
|
1374
2732
|
if (el) {
|
|
2733
|
+
if (!this.isSessionTimelineNodeKey(messageKey)) return;
|
|
2734
|
+
if (this.sessionMessageRefMap[messageKey] === el) return;
|
|
1375
2735
|
this.sessionMessageRefMap[messageKey] = el;
|
|
2736
|
+
this.invalidateSessionTimelineMeasurementCache();
|
|
1376
2737
|
} else {
|
|
2738
|
+
if (!this.sessionMessageRefMap[messageKey]) return;
|
|
1377
2739
|
delete this.sessionMessageRefMap[messageKey];
|
|
2740
|
+
this.invalidateSessionTimelineMeasurementCache();
|
|
2741
|
+
}
|
|
2742
|
+
},
|
|
2743
|
+
isSessionTimelineNodeKey(messageKey) {
|
|
2744
|
+
if (!messageKey) return false;
|
|
2745
|
+
return !!(this.sessionTimelineNodeKeyMap && this.sessionTimelineNodeKeyMap[messageKey]);
|
|
2746
|
+
},
|
|
2747
|
+
pruneSessionMessageRefs() {
|
|
2748
|
+
const nodeKeyMap = this.sessionTimelineNodeKeyMap || Object.create(null);
|
|
2749
|
+
let removed = false;
|
|
2750
|
+
for (const key of Object.keys(this.sessionMessageRefMap)) {
|
|
2751
|
+
if (nodeKeyMap[key]) continue;
|
|
2752
|
+
delete this.sessionMessageRefMap[key];
|
|
2753
|
+
removed = true;
|
|
2754
|
+
}
|
|
2755
|
+
for (const key of Object.keys(this.sessionMessageRefBinderMap)) {
|
|
2756
|
+
if (nodeKeyMap[key]) continue;
|
|
2757
|
+
delete this.sessionMessageRefBinderMap[key];
|
|
2758
|
+
}
|
|
2759
|
+
if (removed) {
|
|
2760
|
+
this.invalidateSessionTimelineMeasurementCache();
|
|
1378
2761
|
}
|
|
1379
2762
|
},
|
|
1380
2763
|
cancelSessionTimelineSync() {
|
|
@@ -1396,42 +2779,178 @@ import { createSkillsMethods } from './modules/skills.methods.mjs';
|
|
|
1396
2779
|
this.syncSessionTimelineActiveFromScroll();
|
|
1397
2780
|
},
|
|
1398
2781
|
onSessionPreviewScroll() {
|
|
2782
|
+
if (
|
|
2783
|
+
!this.sessionTimelineEnabled
|
|
2784
|
+
|| this.mainTab !== 'sessions'
|
|
2785
|
+
|| this.getMainTabForNav() !== 'sessions'
|
|
2786
|
+
|| !this.sessionPreviewRenderEnabled
|
|
2787
|
+
) return;
|
|
2788
|
+
if (!this.sessionTimelineNodes.length) return;
|
|
2789
|
+
const scrollEl = this.sessionPreviewScrollEl || this.$refs.sessionPreviewScroll;
|
|
2790
|
+
if (!scrollEl) return;
|
|
2791
|
+
const now = Date.now();
|
|
2792
|
+
const currentTop = Number(scrollEl.scrollTop || 0);
|
|
2793
|
+
const delta = Math.abs(currentTop - Number(this.sessionTimelineLastScrollTop || 0));
|
|
2794
|
+
const elapsed = now - Number(this.sessionTimelineLastSyncAt || 0);
|
|
2795
|
+
if (delta < 48 && elapsed < 120) {
|
|
2796
|
+
return;
|
|
2797
|
+
}
|
|
2798
|
+
this.sessionTimelineLastScrollTop = currentTop;
|
|
2799
|
+
this.sessionTimelineLastSyncAt = now;
|
|
1399
2800
|
this.scheduleSessionTimelineSync();
|
|
1400
2801
|
},
|
|
1401
2802
|
onWindowResize() {
|
|
2803
|
+
this.updateCompactLayoutMode();
|
|
2804
|
+
if (
|
|
2805
|
+
!this.sessionTimelineEnabled
|
|
2806
|
+
|| this.mainTab !== 'sessions'
|
|
2807
|
+
|| this.getMainTabForNav() !== 'sessions'
|
|
2808
|
+
|| !this.sessionPreviewRenderEnabled
|
|
2809
|
+
) {
|
|
2810
|
+
return;
|
|
2811
|
+
}
|
|
2812
|
+
if (!this.sessionTimelineNodes.length) return;
|
|
1402
2813
|
this.updateSessionTimelineOffset();
|
|
2814
|
+
this.invalidateSessionTimelineMeasurementCache();
|
|
1403
2815
|
this.scheduleSessionTimelineSync();
|
|
1404
2816
|
},
|
|
2817
|
+
shouldForceCompactLayout() {
|
|
2818
|
+
if (typeof window === 'undefined' || typeof navigator === 'undefined') {
|
|
2819
|
+
return false;
|
|
2820
|
+
}
|
|
2821
|
+
const doc = typeof document !== 'undefined' ? document : null;
|
|
2822
|
+
const viewportWidth = Math.max(
|
|
2823
|
+
0,
|
|
2824
|
+
Number(window.innerWidth || 0),
|
|
2825
|
+
Number(doc && doc.documentElement ? doc.documentElement.clientWidth : 0)
|
|
2826
|
+
);
|
|
2827
|
+
const screenWidth = Number(window.screen && window.screen.width ? window.screen.width : 0);
|
|
2828
|
+
const screenHeight = Number(window.screen && window.screen.height ? window.screen.height : 0);
|
|
2829
|
+
const shortEdge = screenWidth > 0 && screenHeight > 0
|
|
2830
|
+
? Math.min(screenWidth, screenHeight)
|
|
2831
|
+
: 0;
|
|
2832
|
+
const touchPoints = Number(navigator.maxTouchPoints || 0);
|
|
2833
|
+
const userAgent = String(navigator.userAgent || '');
|
|
2834
|
+
const isMobileUa = /(Android|iPhone|iPad|iPod|Mobile)/i.test(userAgent);
|
|
2835
|
+
let coarsePointer = false;
|
|
2836
|
+
let noHover = false;
|
|
2837
|
+
try {
|
|
2838
|
+
coarsePointer = !!(window.matchMedia && window.matchMedia('(pointer: coarse)').matches);
|
|
2839
|
+
} catch (_) {}
|
|
2840
|
+
try {
|
|
2841
|
+
noHover = !!(window.matchMedia && window.matchMedia('(hover: none)').matches);
|
|
2842
|
+
} catch (_) {}
|
|
2843
|
+
return shouldForceCompactLayoutMode({
|
|
2844
|
+
viewportWidth,
|
|
2845
|
+
screenWidth,
|
|
2846
|
+
screenHeight,
|
|
2847
|
+
shortEdge,
|
|
2848
|
+
maxTouchPoints: touchPoints,
|
|
2849
|
+
userAgent,
|
|
2850
|
+
isMobileUa,
|
|
2851
|
+
coarsePointer,
|
|
2852
|
+
noHover
|
|
2853
|
+
});
|
|
2854
|
+
},
|
|
2855
|
+
applyCompactLayoutClass(enabled) {
|
|
2856
|
+
if (typeof document === 'undefined' || !document.body) {
|
|
2857
|
+
return;
|
|
2858
|
+
}
|
|
2859
|
+
document.body.classList.toggle('force-compact', !!enabled);
|
|
2860
|
+
},
|
|
2861
|
+
updateCompactLayoutMode() {
|
|
2862
|
+
const enabled = this.shouldForceCompactLayout();
|
|
2863
|
+
this.forceCompactLayout = enabled;
|
|
2864
|
+
this.applyCompactLayoutClass(enabled);
|
|
2865
|
+
},
|
|
1405
2866
|
syncSessionTimelineActiveFromScroll() {
|
|
2867
|
+
if (
|
|
2868
|
+
!this.sessionTimelineEnabled
|
|
2869
|
+
|| this.mainTab !== 'sessions'
|
|
2870
|
+
|| this.getMainTabForNav() !== 'sessions'
|
|
2871
|
+
|| !this.sessionPreviewRenderEnabled
|
|
2872
|
+
) {
|
|
2873
|
+
if (this.sessionTimelineActiveKey) {
|
|
2874
|
+
this.sessionTimelineActiveKey = '';
|
|
2875
|
+
}
|
|
2876
|
+
return;
|
|
2877
|
+
}
|
|
1406
2878
|
const nodes = Array.isArray(this.sessionTimelineNodes) ? this.sessionTimelineNodes : [];
|
|
1407
2879
|
if (!nodes.length) {
|
|
1408
|
-
this.sessionTimelineActiveKey
|
|
2880
|
+
if (this.sessionTimelineActiveKey) {
|
|
2881
|
+
this.sessionTimelineActiveKey = '';
|
|
2882
|
+
}
|
|
1409
2883
|
return;
|
|
1410
2884
|
}
|
|
2885
|
+
this.pruneSessionMessageRefs();
|
|
1411
2886
|
const scrollEl = this.sessionPreviewScrollEl || this.$refs.sessionPreviewScroll;
|
|
1412
2887
|
if (!scrollEl) {
|
|
1413
|
-
this.sessionTimelineActiveKey
|
|
2888
|
+
if (!this.isSessionTimelineNodeKey(this.sessionTimelineActiveKey)) {
|
|
2889
|
+
const fallbackKey = nodes[0].key;
|
|
2890
|
+
if (this.sessionTimelineActiveKey !== fallbackKey) {
|
|
2891
|
+
this.sessionTimelineActiveKey = fallbackKey;
|
|
2892
|
+
}
|
|
2893
|
+
}
|
|
1414
2894
|
return;
|
|
1415
2895
|
}
|
|
1416
|
-
const scrollRect = scrollEl.getBoundingClientRect();
|
|
1417
2896
|
const headerEl = scrollEl.querySelector('.session-preview-header');
|
|
1418
|
-
const
|
|
1419
|
-
const
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
2897
|
+
const stickyOffset = headerEl ? (headerEl.offsetHeight + 8) : 8;
|
|
2898
|
+
const rawAnchorY = Number(scrollEl.scrollTop || 0) + stickyOffset;
|
|
2899
|
+
const previousAnchorY = Number(this.sessionTimelineLastAnchorY || 0);
|
|
2900
|
+
let direction = rawAnchorY - previousAnchorY;
|
|
2901
|
+
if (Math.abs(direction) < 1) {
|
|
2902
|
+
direction = Number(this.sessionTimelineLastDirection || 0);
|
|
2903
|
+
} else {
|
|
2904
|
+
this.sessionTimelineLastDirection = direction > 0 ? 1 : -1;
|
|
2905
|
+
}
|
|
2906
|
+
this.sessionTimelineLastAnchorY = rawAnchorY;
|
|
2907
|
+
const hysteresisPx = 18;
|
|
2908
|
+
const hysteresis = direction > 0 ? -hysteresisPx : (direction < 0 ? hysteresisPx : 0);
|
|
2909
|
+
const anchorY = rawAnchorY + hysteresis;
|
|
2910
|
+
const measuredNodes = this.getCachedSessionTimelineMeasuredNodes(nodes);
|
|
2911
|
+
if (!measuredNodes.length) {
|
|
2912
|
+
if (!this.isSessionTimelineNodeKey(this.sessionTimelineActiveKey)) {
|
|
2913
|
+
this.sessionTimelineActiveKey = nodes[0].key;
|
|
2914
|
+
}
|
|
2915
|
+
return;
|
|
2916
|
+
}
|
|
2917
|
+
let low = 0;
|
|
2918
|
+
let high = measuredNodes.length - 1;
|
|
2919
|
+
let candidateIndex = 0;
|
|
2920
|
+
while (low <= high) {
|
|
2921
|
+
const mid = Math.floor((low + high) / 2);
|
|
2922
|
+
if (measuredNodes[mid].top <= anchorY) {
|
|
2923
|
+
candidateIndex = mid;
|
|
2924
|
+
low = mid + 1;
|
|
2925
|
+
} else {
|
|
2926
|
+
high = mid - 1;
|
|
2927
|
+
}
|
|
2928
|
+
}
|
|
2929
|
+
let currentIndex = -1;
|
|
2930
|
+
if (this.sessionTimelineActiveKey) {
|
|
2931
|
+
for (let i = 0; i < measuredNodes.length; i += 1) {
|
|
2932
|
+
if (measuredNodes[i].key === this.sessionTimelineActiveKey) {
|
|
2933
|
+
currentIndex = i;
|
|
2934
|
+
break;
|
|
2935
|
+
}
|
|
2936
|
+
}
|
|
2937
|
+
}
|
|
2938
|
+
if (currentIndex >= 0) {
|
|
2939
|
+
if (direction > 0 && candidateIndex < currentIndex) {
|
|
2940
|
+
candidateIndex = currentIndex;
|
|
2941
|
+
} else if (direction < 0 && candidateIndex > currentIndex) {
|
|
2942
|
+
candidateIndex = currentIndex;
|
|
1428
2943
|
}
|
|
1429
|
-
break;
|
|
1430
2944
|
}
|
|
1431
|
-
|
|
2945
|
+
const activeKey = measuredNodes[candidateIndex].key;
|
|
2946
|
+
if (this.sessionTimelineActiveKey !== activeKey) {
|
|
2947
|
+
this.sessionTimelineActiveKey = activeKey;
|
|
2948
|
+
}
|
|
1432
2949
|
},
|
|
1433
2950
|
jumpToSessionTimelineNode(messageKey) {
|
|
2951
|
+
if (!this.sessionTimelineEnabled || this.mainTab !== 'sessions' || !this.sessionPreviewRenderEnabled) return;
|
|
1434
2952
|
if (!messageKey) return;
|
|
2953
|
+
if (!this.isSessionTimelineNodeKey(messageKey)) return;
|
|
1435
2954
|
const scrollEl = this.sessionPreviewScrollEl || this.$refs.sessionPreviewScroll;
|
|
1436
2955
|
if (!scrollEl) return;
|
|
1437
2956
|
const messageEl = this.sessionMessageRefMap[messageKey];
|
|
@@ -1504,67 +3023,9 @@ import { createSkillsMethods } from './modules/skills.methods.mjs';
|
|
|
1504
3023
|
},
|
|
1505
3024
|
|
|
1506
3025
|
async loadSessions() {
|
|
1507
|
-
|
|
1508
|
-
this.
|
|
1509
|
-
|
|
1510
|
-
const params = buildSessionListParams({
|
|
1511
|
-
source: this.sessionFilterSource,
|
|
1512
|
-
pathFilter: this.sessionPathFilter,
|
|
1513
|
-
query: this.sessionQuery,
|
|
1514
|
-
roleFilter: this.sessionRoleFilter,
|
|
1515
|
-
timeRangePreset: this.sessionTimePreset
|
|
1516
|
-
});
|
|
1517
|
-
try {
|
|
1518
|
-
const res = await api('list-sessions', params);
|
|
1519
|
-
if (res.error) {
|
|
1520
|
-
this.showMessage(res.error, 'error');
|
|
1521
|
-
this.sessionsList = [];
|
|
1522
|
-
this.activeSession = null;
|
|
1523
|
-
this.activeSessionMessages = [];
|
|
1524
|
-
this.activeSessionDetailClipped = false;
|
|
1525
|
-
this.cancelSessionTimelineSync();
|
|
1526
|
-
this.sessionTimelineActiveKey = '';
|
|
1527
|
-
this.sessionMessageRefMap = Object.create(null);
|
|
1528
|
-
} else {
|
|
1529
|
-
this.sessionsList = Array.isArray(res.sessions) ? res.sessions : [];
|
|
1530
|
-
this.syncSessionPathOptionsForSource(
|
|
1531
|
-
this.sessionFilterSource,
|
|
1532
|
-
this.extractPathOptionsFromSessions(this.sessionsList),
|
|
1533
|
-
true
|
|
1534
|
-
);
|
|
1535
|
-
if (this.sessionsList.length === 0) {
|
|
1536
|
-
this.activeSession = null;
|
|
1537
|
-
this.activeSessionMessages = [];
|
|
1538
|
-
this.activeSessionDetailClipped = false;
|
|
1539
|
-
this.cancelSessionTimelineSync();
|
|
1540
|
-
this.sessionTimelineActiveKey = '';
|
|
1541
|
-
this.sessionMessageRefMap = Object.create(null);
|
|
1542
|
-
} else {
|
|
1543
|
-
const oldKey = this.activeSession ? this.getSessionExportKey(this.activeSession) : '';
|
|
1544
|
-
const matched = this.sessionsList.find(item => this.getSessionExportKey(item) === oldKey);
|
|
1545
|
-
this.activeSession = matched || this.sessionsList[0];
|
|
1546
|
-
this.activeSessionMessages = [];
|
|
1547
|
-
this.activeSessionDetailError = '';
|
|
1548
|
-
this.activeSessionDetailClipped = false;
|
|
1549
|
-
this.cancelSessionTimelineSync();
|
|
1550
|
-
this.sessionTimelineActiveKey = '';
|
|
1551
|
-
this.sessionMessageRefMap = Object.create(null);
|
|
1552
|
-
await this.loadActiveSessionDetail();
|
|
1553
|
-
}
|
|
1554
|
-
void this.loadSessionPathOptions({ source: this.sessionFilterSource });
|
|
1555
|
-
}
|
|
1556
|
-
} catch (e) {
|
|
1557
|
-
this.sessionsList = [];
|
|
1558
|
-
this.activeSession = null;
|
|
1559
|
-
this.activeSessionMessages = [];
|
|
1560
|
-
this.activeSessionDetailClipped = false;
|
|
1561
|
-
this.cancelSessionTimelineSync();
|
|
1562
|
-
this.sessionTimelineActiveKey = '';
|
|
1563
|
-
this.sessionMessageRefMap = Object.create(null);
|
|
1564
|
-
this.showMessage('加载会话失败', 'error');
|
|
1565
|
-
} finally {
|
|
1566
|
-
this.sessionsLoading = false;
|
|
1567
|
-
}
|
|
3026
|
+
const result = await loadSessionsHelper.call(this, api);
|
|
3027
|
+
this.pruneSessionPinnedMap(this.sessionsList);
|
|
3028
|
+
return result;
|
|
1568
3029
|
},
|
|
1569
3030
|
|
|
1570
3031
|
async selectSession(session) {
|
|
@@ -1572,11 +3033,13 @@ import { createSkillsMethods } from './modules/skills.methods.mjs';
|
|
|
1572
3033
|
if (this.activeSession && this.getSessionExportKey(this.activeSession) === this.getSessionExportKey(session)) return;
|
|
1573
3034
|
this.activeSession = session;
|
|
1574
3035
|
this.activeSessionMessages = [];
|
|
3036
|
+
this.resetSessionDetailPagination();
|
|
3037
|
+
this.resetSessionPreviewMessageRender();
|
|
1575
3038
|
this.activeSessionDetailError = '';
|
|
1576
3039
|
this.activeSessionDetailClipped = false;
|
|
1577
3040
|
this.cancelSessionTimelineSync();
|
|
1578
3041
|
this.sessionTimelineActiveKey = '';
|
|
1579
|
-
this.
|
|
3042
|
+
this.clearSessionTimelineRefs();
|
|
1580
3043
|
await this.loadActiveSessionDetail();
|
|
1581
3044
|
},
|
|
1582
3045
|
|
|
@@ -1625,87 +3088,8 @@ import { createSkillsMethods } from './modules/skills.methods.mjs';
|
|
|
1625
3088
|
}
|
|
1626
3089
|
},
|
|
1627
3090
|
|
|
1628
|
-
async loadActiveSessionDetail() {
|
|
1629
|
-
|
|
1630
|
-
this.activeSessionMessages = [];
|
|
1631
|
-
this.activeSessionDetailError = '';
|
|
1632
|
-
this.activeSessionDetailClipped = false;
|
|
1633
|
-
this.cancelSessionTimelineSync();
|
|
1634
|
-
this.sessionTimelineActiveKey = '';
|
|
1635
|
-
this.sessionMessageRefMap = Object.create(null);
|
|
1636
|
-
return;
|
|
1637
|
-
}
|
|
1638
|
-
|
|
1639
|
-
const requestSeq = ++this.sessionDetailRequestSeq;
|
|
1640
|
-
this.sessionDetailLoading = true;
|
|
1641
|
-
this.activeSessionDetailError = '';
|
|
1642
|
-
try {
|
|
1643
|
-
const res = await api('session-detail', {
|
|
1644
|
-
source: this.activeSession.source,
|
|
1645
|
-
sessionId: this.activeSession.sessionId,
|
|
1646
|
-
filePath: this.activeSession.filePath,
|
|
1647
|
-
messageLimit: 300
|
|
1648
|
-
});
|
|
1649
|
-
|
|
1650
|
-
if (requestSeq !== this.sessionDetailRequestSeq) {
|
|
1651
|
-
return;
|
|
1652
|
-
}
|
|
1653
|
-
|
|
1654
|
-
if (res.error) {
|
|
1655
|
-
this.activeSessionMessages = [];
|
|
1656
|
-
this.activeSessionDetailClipped = false;
|
|
1657
|
-
this.activeSessionDetailError = res.error;
|
|
1658
|
-
this.cancelSessionTimelineSync();
|
|
1659
|
-
this.sessionTimelineActiveKey = '';
|
|
1660
|
-
this.sessionMessageRefMap = Object.create(null);
|
|
1661
|
-
return;
|
|
1662
|
-
}
|
|
1663
|
-
|
|
1664
|
-
const rawMessages = Array.isArray(res.messages) ? res.messages : [];
|
|
1665
|
-
this.activeSessionMessages = rawMessages.map((message) => this.normalizeSessionMessage(message));
|
|
1666
|
-
this.activeSessionDetailClipped = !!res.clipped;
|
|
1667
|
-
if (this.activeSession) {
|
|
1668
|
-
if (res.sourceLabel) {
|
|
1669
|
-
this.activeSession.sourceLabel = res.sourceLabel;
|
|
1670
|
-
}
|
|
1671
|
-
if (res.sessionId) {
|
|
1672
|
-
this.activeSession.sessionId = res.sessionId;
|
|
1673
|
-
if (!this.activeSession.title) {
|
|
1674
|
-
this.activeSession.title = res.sessionId;
|
|
1675
|
-
}
|
|
1676
|
-
}
|
|
1677
|
-
if (res.filePath) {
|
|
1678
|
-
this.activeSession.filePath = res.filePath;
|
|
1679
|
-
}
|
|
1680
|
-
}
|
|
1681
|
-
if (res.updatedAt) {
|
|
1682
|
-
this.activeSession.updatedAt = res.updatedAt;
|
|
1683
|
-
}
|
|
1684
|
-
if (res.cwd) {
|
|
1685
|
-
this.activeSession.cwd = res.cwd;
|
|
1686
|
-
}
|
|
1687
|
-
if (Number.isFinite(res.totalMessages)) {
|
|
1688
|
-
this.syncActiveSessionMessageCount(res.totalMessages);
|
|
1689
|
-
}
|
|
1690
|
-
this.$nextTick(() => {
|
|
1691
|
-
this.updateSessionTimelineOffset();
|
|
1692
|
-
this.scheduleSessionTimelineSync();
|
|
1693
|
-
});
|
|
1694
|
-
} catch (e) {
|
|
1695
|
-
if (requestSeq !== this.sessionDetailRequestSeq) {
|
|
1696
|
-
return;
|
|
1697
|
-
}
|
|
1698
|
-
this.activeSessionMessages = [];
|
|
1699
|
-
this.activeSessionDetailClipped = false;
|
|
1700
|
-
this.activeSessionDetailError = '加载会话内容失败: ' + e.message;
|
|
1701
|
-
this.cancelSessionTimelineSync();
|
|
1702
|
-
this.sessionTimelineActiveKey = '';
|
|
1703
|
-
this.sessionMessageRefMap = Object.create(null);
|
|
1704
|
-
} finally {
|
|
1705
|
-
if (requestSeq === this.sessionDetailRequestSeq) {
|
|
1706
|
-
this.sessionDetailLoading = false;
|
|
1707
|
-
}
|
|
1708
|
-
}
|
|
3091
|
+
async loadActiveSessionDetail(options = {}) {
|
|
3092
|
+
return loadActiveSessionDetailHelper.call(this, api, options);
|
|
1709
3093
|
},
|
|
1710
3094
|
|
|
1711
3095
|
downloadTextFile(fileName, content, mimeType = 'text/markdown;charset=utf-8') {
|
|
@@ -1751,17 +3135,75 @@ import { createSkillsMethods } from './modules/skills.methods.mjs';
|
|
|
1751
3135
|
}
|
|
1752
3136
|
},
|
|
1753
3137
|
|
|
1754
|
-
async
|
|
3138
|
+
async quickSwitchProvider(name) {
|
|
3139
|
+
const target = String(name || '').trim();
|
|
3140
|
+
if (!target || target === this.pendingProviderSwitch) {
|
|
3141
|
+
return;
|
|
3142
|
+
}
|
|
3143
|
+
if (!this.providerSwitchInProgress && target === this.currentProvider) {
|
|
3144
|
+
return;
|
|
3145
|
+
}
|
|
3146
|
+
await this.switchProvider(target);
|
|
3147
|
+
},
|
|
3148
|
+
|
|
3149
|
+
async waitForCodexApplyIdle(maxWaitMs = 20000) {
|
|
3150
|
+
const startedAt = Date.now();
|
|
3151
|
+
while (this.codexApplying) {
|
|
3152
|
+
if ((Date.now() - startedAt) > maxWaitMs) {
|
|
3153
|
+
throw new Error('等待配置应用完成超时');
|
|
3154
|
+
}
|
|
3155
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
3156
|
+
}
|
|
3157
|
+
},
|
|
3158
|
+
|
|
3159
|
+
async performProviderSwitch(name) {
|
|
3160
|
+
await this.waitForCodexApplyIdle();
|
|
1755
3161
|
this.currentProvider = name;
|
|
1756
3162
|
await this.loadModelsForProvider(name);
|
|
1757
3163
|
if (this.modelsSource === 'remote' && this.models.length > 0 && !this.models.includes(this.currentModel)) {
|
|
1758
3164
|
this.currentModel = this.models[0];
|
|
1759
3165
|
}
|
|
1760
3166
|
if (getProviderConfigModeMeta(this.configMode)) {
|
|
3167
|
+
await this.waitForCodexApplyIdle();
|
|
1761
3168
|
await this.applyCodexConfigDirect({ silent: true });
|
|
1762
3169
|
}
|
|
1763
3170
|
},
|
|
1764
3171
|
|
|
3172
|
+
async switchProvider(name) {
|
|
3173
|
+
const target = String(name || '').trim();
|
|
3174
|
+
if (!target) {
|
|
3175
|
+
return;
|
|
3176
|
+
}
|
|
3177
|
+
if (this.providerSwitchInProgress) {
|
|
3178
|
+
this.pendingProviderSwitch = target;
|
|
3179
|
+
return;
|
|
3180
|
+
}
|
|
3181
|
+
this.providerSwitchInProgress = true;
|
|
3182
|
+
let lastError = '';
|
|
3183
|
+
try {
|
|
3184
|
+
this.pendingProviderSwitch = '';
|
|
3185
|
+
const result = await runLatestOnlyQueue(target, {
|
|
3186
|
+
perform: async (queuedTarget) => {
|
|
3187
|
+
await this.performProviderSwitch(queuedTarget);
|
|
3188
|
+
},
|
|
3189
|
+
consumePending: () => {
|
|
3190
|
+
const queued = this.pendingProviderSwitch;
|
|
3191
|
+
this.pendingProviderSwitch = '';
|
|
3192
|
+
return queued;
|
|
3193
|
+
}
|
|
3194
|
+
});
|
|
3195
|
+
if (result && typeof result.lastError === 'string') {
|
|
3196
|
+
lastError = result.lastError;
|
|
3197
|
+
}
|
|
3198
|
+
} finally {
|
|
3199
|
+
this.providerSwitchInProgress = false;
|
|
3200
|
+
this.pendingProviderSwitch = '';
|
|
3201
|
+
}
|
|
3202
|
+
if (lastError) {
|
|
3203
|
+
this.showMessage(lastError, 'error');
|
|
3204
|
+
}
|
|
3205
|
+
},
|
|
3206
|
+
|
|
1765
3207
|
async onModelChange() {
|
|
1766
3208
|
await this.applyCodexConfigDirect();
|
|
1767
3209
|
},
|
|
@@ -1952,9 +3394,11 @@ import { createSkillsMethods } from './modules/skills.methods.mjs';
|
|
|
1952
3394
|
return;
|
|
1953
3395
|
}
|
|
1954
3396
|
this.agentsContent = res.content || '';
|
|
3397
|
+
this.agentsOriginalContent = this.agentsContent;
|
|
1955
3398
|
this.agentsPath = res.path || '';
|
|
1956
3399
|
this.agentsExists = !!res.exists;
|
|
1957
3400
|
this.agentsLineEnding = res.lineEnding === '\r\n' ? '\r\n' : '\n';
|
|
3401
|
+
this.resetAgentsDiffState();
|
|
1958
3402
|
this.showAgentsModal = true;
|
|
1959
3403
|
} catch (e) {
|
|
1960
3404
|
this.showMessage('加载文件失败', 'error');
|
|
@@ -1963,8 +3407,8 @@ import { createSkillsMethods } from './modules/skills.methods.mjs';
|
|
|
1963
3407
|
}
|
|
1964
3408
|
},
|
|
1965
3409
|
|
|
1966
|
-
...createSkillsMethods({ api }),
|
|
1967
|
-
|
|
3410
|
+
...createSkillsMethods({ api }),
|
|
3411
|
+
|
|
1968
3412
|
async openOpenclawAgentsEditor() {
|
|
1969
3413
|
this.setAgentsModalContext('openclaw');
|
|
1970
3414
|
this.agentsLoading = true;
|
|
@@ -1978,9 +3422,11 @@ import { createSkillsMethods } from './modules/skills.methods.mjs';
|
|
|
1978
3422
|
this.showMessage(`OpenClaw 配置解析失败,已使用默认 Workspace:${res.configError}`, 'error');
|
|
1979
3423
|
}
|
|
1980
3424
|
this.agentsContent = res.content || '';
|
|
3425
|
+
this.agentsOriginalContent = this.agentsContent;
|
|
1981
3426
|
this.agentsPath = res.path || '';
|
|
1982
3427
|
this.agentsExists = !!res.exists;
|
|
1983
3428
|
this.agentsLineEnding = res.lineEnding === '\r\n' ? '\r\n' : '\n';
|
|
3429
|
+
this.resetAgentsDiffState();
|
|
1984
3430
|
this.showAgentsModal = true;
|
|
1985
3431
|
} catch (e) {
|
|
1986
3432
|
this.showMessage('加载文件失败', 'error');
|
|
@@ -2007,9 +3453,11 @@ import { createSkillsMethods } from './modules/skills.methods.mjs';
|
|
|
2007
3453
|
this.showMessage(`OpenClaw 配置解析失败,已使用默认 Workspace:${res.configError}`, 'error');
|
|
2008
3454
|
}
|
|
2009
3455
|
this.agentsContent = res.content || '';
|
|
3456
|
+
this.agentsOriginalContent = this.agentsContent;
|
|
2010
3457
|
this.agentsPath = res.path || '';
|
|
2011
3458
|
this.agentsExists = !!res.exists;
|
|
2012
3459
|
this.agentsLineEnding = res.lineEnding === '\r\n' ? '\r\n' : '\n';
|
|
3460
|
+
this.resetAgentsDiffState();
|
|
2013
3461
|
this.showAgentsModal = true;
|
|
2014
3462
|
} catch (e) {
|
|
2015
3463
|
this.showMessage('加载文件失败', 'error');
|
|
@@ -2038,18 +3486,272 @@ import { createSkillsMethods } from './modules/skills.methods.mjs';
|
|
|
2038
3486
|
this.agentsWorkspaceFileName = '';
|
|
2039
3487
|
},
|
|
2040
3488
|
|
|
2041
|
-
|
|
3489
|
+
resetAgentsDiffState() {
|
|
3490
|
+
this.agentsDiffVisible = false;
|
|
3491
|
+
this.agentsDiffLoading = false;
|
|
3492
|
+
this.agentsDiffError = '';
|
|
3493
|
+
this.agentsDiffLines = [];
|
|
3494
|
+
this.agentsDiffStats = {
|
|
3495
|
+
added: 0,
|
|
3496
|
+
removed: 0,
|
|
3497
|
+
unchanged: 0
|
|
3498
|
+
};
|
|
3499
|
+
this.agentsDiffTruncated = false;
|
|
3500
|
+
this.agentsDiffHasChangesValue = false;
|
|
3501
|
+
this.agentsDiffFingerprint = '';
|
|
3502
|
+
this._agentsDiffPreviewRequestToken = null;
|
|
3503
|
+
},
|
|
3504
|
+
handleGlobalKeydown(event) {
|
|
3505
|
+
if (!event || event.key !== 'Escape') {
|
|
3506
|
+
return;
|
|
3507
|
+
}
|
|
3508
|
+
if (this.showConfirmDialog) {
|
|
3509
|
+
event.preventDefault();
|
|
3510
|
+
event.stopPropagation();
|
|
3511
|
+
this.resolveConfirmDialog(false);
|
|
3512
|
+
return;
|
|
3513
|
+
}
|
|
3514
|
+
if (!this.showAgentsModal) {
|
|
3515
|
+
return;
|
|
3516
|
+
}
|
|
3517
|
+
event.preventDefault();
|
|
3518
|
+
event.stopPropagation();
|
|
3519
|
+
if (this.agentsSaving || this.agentsDiffLoading) {
|
|
3520
|
+
return;
|
|
3521
|
+
}
|
|
3522
|
+
if (this.agentsDiffVisible) {
|
|
3523
|
+
this.resetAgentsDiffState();
|
|
3524
|
+
return;
|
|
3525
|
+
}
|
|
3526
|
+
this.closeAgentsModal();
|
|
3527
|
+
},
|
|
3528
|
+
hasPendingAgentsDraft() {
|
|
3529
|
+
if (!this.showAgentsModal || this.agentsLoading || this.agentsSaving) {
|
|
3530
|
+
return false;
|
|
3531
|
+
}
|
|
3532
|
+
return this.hasAgentsContentChanged() || this.agentsDiffVisible;
|
|
3533
|
+
},
|
|
3534
|
+
handleBeforeUnload(event) {
|
|
3535
|
+
if (!this.hasPendingAgentsDraft()) {
|
|
3536
|
+
return;
|
|
3537
|
+
}
|
|
3538
|
+
if (event && typeof event.preventDefault === 'function') {
|
|
3539
|
+
event.preventDefault();
|
|
3540
|
+
event.returnValue = '';
|
|
3541
|
+
}
|
|
3542
|
+
return '';
|
|
3543
|
+
},
|
|
3544
|
+
hasAgentsContentChanged() {
|
|
3545
|
+
const original = typeof this.agentsOriginalContent === 'string' ? this.agentsOriginalContent : '';
|
|
3546
|
+
const current = typeof this.agentsContent === 'string' ? this.agentsContent : '';
|
|
3547
|
+
return original !== current;
|
|
3548
|
+
},
|
|
3549
|
+
requestConfirmDialog(options = {}) {
|
|
3550
|
+
if (typeof this.confirmDialogResolver === 'function') {
|
|
3551
|
+
this.confirmDialogResolver(false);
|
|
3552
|
+
}
|
|
3553
|
+
this.confirmDialogTitle = typeof options.title === 'string' && options.title.trim()
|
|
3554
|
+
? options.title.trim()
|
|
3555
|
+
: '请确认操作';
|
|
3556
|
+
this.confirmDialogMessage = typeof options.message === 'string' ? options.message : '';
|
|
3557
|
+
this.confirmDialogConfirmText = typeof options.confirmText === 'string' && options.confirmText.trim()
|
|
3558
|
+
? options.confirmText.trim()
|
|
3559
|
+
: '确认';
|
|
3560
|
+
this.confirmDialogCancelText = typeof options.cancelText === 'string' && options.cancelText.trim()
|
|
3561
|
+
? options.cancelText.trim()
|
|
3562
|
+
: '取消';
|
|
3563
|
+
this.confirmDialogDanger = !!options.danger;
|
|
3564
|
+
this.showConfirmDialog = true;
|
|
3565
|
+
return new Promise((resolve) => {
|
|
3566
|
+
this.confirmDialogResolver = resolve;
|
|
3567
|
+
});
|
|
3568
|
+
},
|
|
3569
|
+
resolveConfirmDialog(confirmed) {
|
|
3570
|
+
const resolver = typeof this.confirmDialogResolver === 'function'
|
|
3571
|
+
? this.confirmDialogResolver
|
|
3572
|
+
: null;
|
|
3573
|
+
this.showConfirmDialog = false;
|
|
3574
|
+
this.confirmDialogTitle = '';
|
|
3575
|
+
this.confirmDialogMessage = '';
|
|
3576
|
+
this.confirmDialogConfirmText = '确认';
|
|
3577
|
+
this.confirmDialogCancelText = '取消';
|
|
3578
|
+
this.confirmDialogDanger = false;
|
|
3579
|
+
this.confirmDialogResolver = null;
|
|
3580
|
+
if (resolver) {
|
|
3581
|
+
resolver(!!confirmed);
|
|
3582
|
+
}
|
|
3583
|
+
},
|
|
3584
|
+
closeConfirmDialog() {
|
|
3585
|
+
this.resolveConfirmDialog(false);
|
|
3586
|
+
},
|
|
3587
|
+
onAgentsContentInput() {
|
|
3588
|
+
if (this.agentsDiffVisible || this.agentsDiffLines.length) {
|
|
3589
|
+
this.resetAgentsDiffState();
|
|
3590
|
+
}
|
|
3591
|
+
},
|
|
3592
|
+
buildAgentsDiffFingerprint() {
|
|
3593
|
+
const context = this.agentsContext || 'codex';
|
|
3594
|
+
const fileName = context === 'openclaw-workspace'
|
|
3595
|
+
? (this.agentsWorkspaceFileName || '')
|
|
3596
|
+
: '';
|
|
3597
|
+
const lineEnding = this.agentsLineEnding || '\n';
|
|
3598
|
+
const content = typeof this.agentsContent === 'string' ? this.agentsContent : '';
|
|
3599
|
+
const original = typeof this.agentsOriginalContent === 'string' ? this.agentsOriginalContent : '';
|
|
3600
|
+
return `${context}::${fileName}::${lineEnding}::${content.length}::${content}::${original.length}::${original}`;
|
|
3601
|
+
},
|
|
3602
|
+
async prepareAgentsDiff() {
|
|
3603
|
+
const requestFingerprint = this.buildAgentsDiffFingerprint();
|
|
3604
|
+
const requestToken = Symbol('agents-diff-preview');
|
|
3605
|
+
this._agentsDiffPreviewRequestToken = requestToken;
|
|
3606
|
+
this.agentsDiffVisible = true;
|
|
3607
|
+
this.agentsDiffLoading = true;
|
|
3608
|
+
this.agentsDiffError = '';
|
|
3609
|
+
this.agentsDiffLines = [];
|
|
3610
|
+
this.agentsDiffStats = {
|
|
3611
|
+
added: 0,
|
|
3612
|
+
removed: 0,
|
|
3613
|
+
unchanged: 0
|
|
3614
|
+
};
|
|
3615
|
+
this.agentsDiffTruncated = false;
|
|
3616
|
+
this.agentsDiffHasChangesValue = false;
|
|
3617
|
+
try {
|
|
3618
|
+
const shouldApplyPreviewState = () => shouldApplyAgentsDiffPreviewResponse({
|
|
3619
|
+
isVisible: this.agentsDiffVisible,
|
|
3620
|
+
requestToken,
|
|
3621
|
+
activeRequestToken: this._agentsDiffPreviewRequestToken,
|
|
3622
|
+
requestFingerprint,
|
|
3623
|
+
currentFingerprint: this.buildAgentsDiffFingerprint()
|
|
3624
|
+
});
|
|
3625
|
+
const applyPreviewState = (diff) => {
|
|
3626
|
+
if (!shouldApplyPreviewState()) {
|
|
3627
|
+
return false;
|
|
3628
|
+
}
|
|
3629
|
+
const normalizedDiff = diff && typeof diff === 'object' ? diff : {};
|
|
3630
|
+
const rawLines = Array.isArray(normalizedDiff.lines) ? normalizedDiff.lines : [];
|
|
3631
|
+
this.agentsDiffLines = rawLines.filter(line => line && line.type);
|
|
3632
|
+
this.agentsDiffTruncated = !!normalizedDiff.truncated;
|
|
3633
|
+
this.agentsDiffHasChangesValue = !!normalizedDiff.hasChanges;
|
|
3634
|
+
if (normalizedDiff.stats && typeof normalizedDiff.stats === 'object') {
|
|
3635
|
+
this.agentsDiffStats = {
|
|
3636
|
+
added: Number(normalizedDiff.stats.added || 0),
|
|
3637
|
+
removed: Number(normalizedDiff.stats.removed || 0),
|
|
3638
|
+
unchanged: Number(normalizedDiff.stats.unchanged || 0)
|
|
3639
|
+
};
|
|
3640
|
+
} else {
|
|
3641
|
+
const stats = { added: 0, removed: 0, unchanged: 0 };
|
|
3642
|
+
for (const line of this.agentsDiffLines) {
|
|
3643
|
+
if (line && line.type === 'add') stats.added += 1;
|
|
3644
|
+
else if (line && line.type === 'del') stats.removed += 1;
|
|
3645
|
+
else stats.unchanged += 1;
|
|
3646
|
+
}
|
|
3647
|
+
this.agentsDiffStats = stats;
|
|
3648
|
+
}
|
|
3649
|
+
this.agentsDiffFingerprint = requestFingerprint;
|
|
3650
|
+
return true;
|
|
3651
|
+
};
|
|
3652
|
+
const previewRequest = buildAgentsDiffPreviewRequest({
|
|
3653
|
+
baseContent: this.agentsOriginalContent,
|
|
3654
|
+
content: this.agentsContent,
|
|
3655
|
+
lineEnding: this.agentsLineEnding,
|
|
3656
|
+
context: this.agentsContext,
|
|
3657
|
+
fileName: this.agentsWorkspaceFileName
|
|
3658
|
+
});
|
|
3659
|
+
if (previewRequest.exceedsBodyLimit) {
|
|
3660
|
+
applyPreviewState(buildAgentsDiffPreview({
|
|
3661
|
+
baseContent: this.agentsOriginalContent,
|
|
3662
|
+
content: this.agentsContent
|
|
3663
|
+
}));
|
|
3664
|
+
return;
|
|
3665
|
+
}
|
|
3666
|
+
const res = await apiWithMeta('preview-agents-diff', previewRequest.params);
|
|
3667
|
+
if (!shouldApplyPreviewState()) {
|
|
3668
|
+
return;
|
|
3669
|
+
}
|
|
3670
|
+
if (res.error) {
|
|
3671
|
+
if (isAgentsDiffPreviewPayloadTooLarge(res)) {
|
|
3672
|
+
applyPreviewState(buildAgentsDiffPreview({
|
|
3673
|
+
baseContent: this.agentsOriginalContent,
|
|
3674
|
+
content: this.agentsContent
|
|
3675
|
+
}));
|
|
3676
|
+
return;
|
|
3677
|
+
}
|
|
3678
|
+
this.agentsDiffError = res.error;
|
|
3679
|
+
return;
|
|
3680
|
+
}
|
|
3681
|
+
applyPreviewState(res.diff);
|
|
3682
|
+
} catch (e) {
|
|
3683
|
+
if (shouldApplyAgentsDiffPreviewResponse({
|
|
3684
|
+
isVisible: this.agentsDiffVisible,
|
|
3685
|
+
requestToken,
|
|
3686
|
+
activeRequestToken: this._agentsDiffPreviewRequestToken,
|
|
3687
|
+
requestFingerprint,
|
|
3688
|
+
currentFingerprint: this.buildAgentsDiffFingerprint()
|
|
3689
|
+
})) {
|
|
3690
|
+
this.agentsDiffError = '生成差异失败';
|
|
3691
|
+
}
|
|
3692
|
+
} finally {
|
|
3693
|
+
if (this._agentsDiffPreviewRequestToken === requestToken) {
|
|
3694
|
+
this.agentsDiffLoading = false;
|
|
3695
|
+
}
|
|
3696
|
+
}
|
|
3697
|
+
},
|
|
3698
|
+
|
|
3699
|
+
async closeAgentsModal(options = {}) {
|
|
3700
|
+
const force = !!(options && options.force);
|
|
3701
|
+
const shouldConfirmClose = !force
|
|
3702
|
+
&& this.hasPendingAgentsDraft();
|
|
3703
|
+
if (shouldConfirmClose) {
|
|
3704
|
+
const message = this.agentsDiffVisible
|
|
3705
|
+
? '当前处于差异预览模式,改动尚未保存。确认放弃改动并关闭吗?'
|
|
3706
|
+
: '存在未保存改动,确认放弃改动并关闭吗?(关闭页面或应用也会丢失改动)';
|
|
3707
|
+
const confirmed = await this.requestConfirmDialog({
|
|
3708
|
+
title: '放弃未保存改动',
|
|
3709
|
+
message,
|
|
3710
|
+
confirmText: '放弃并关闭',
|
|
3711
|
+
cancelText: '继续编辑',
|
|
3712
|
+
danger: true
|
|
3713
|
+
});
|
|
3714
|
+
if (!confirmed) {
|
|
3715
|
+
return;
|
|
3716
|
+
}
|
|
3717
|
+
}
|
|
2042
3718
|
this.showAgentsModal = false;
|
|
2043
3719
|
this.agentsContent = '';
|
|
3720
|
+
this.agentsOriginalContent = '';
|
|
2044
3721
|
this.agentsPath = '';
|
|
2045
3722
|
this.agentsExists = false;
|
|
2046
3723
|
this.agentsLineEnding = '\n';
|
|
2047
3724
|
this.agentsSaving = false;
|
|
2048
3725
|
this.agentsWorkspaceFileName = '';
|
|
3726
|
+
this.resetAgentsDiffState();
|
|
2049
3727
|
this.setAgentsModalContext('codex');
|
|
2050
3728
|
},
|
|
2051
3729
|
|
|
2052
3730
|
async applyAgentsContent() {
|
|
3731
|
+
if (!this.agentsDiffVisible) {
|
|
3732
|
+
if (!this.hasAgentsContentChanged()) {
|
|
3733
|
+
this.showMessage('未检测到改动', 'info');
|
|
3734
|
+
return;
|
|
3735
|
+
}
|
|
3736
|
+
await this.prepareAgentsDiff();
|
|
3737
|
+
return;
|
|
3738
|
+
}
|
|
3739
|
+
if (this.agentsDiffLoading) {
|
|
3740
|
+
return;
|
|
3741
|
+
}
|
|
3742
|
+
if (this.agentsDiffError) {
|
|
3743
|
+
this.showMessage(this.agentsDiffError, 'error');
|
|
3744
|
+
return;
|
|
3745
|
+
}
|
|
3746
|
+
const fingerprint = this.buildAgentsDiffFingerprint();
|
|
3747
|
+
if (this.agentsDiffFingerprint !== fingerprint) {
|
|
3748
|
+
await this.prepareAgentsDiff();
|
|
3749
|
+
return;
|
|
3750
|
+
}
|
|
3751
|
+
if (!this.agentsDiffHasChanges) {
|
|
3752
|
+
this.showMessage('未检测到改动', 'info');
|
|
3753
|
+
return;
|
|
3754
|
+
}
|
|
2053
3755
|
this.agentsSaving = true;
|
|
2054
3756
|
try {
|
|
2055
3757
|
let action = 'apply-agents-file';
|
|
@@ -2072,7 +3774,7 @@ import { createSkillsMethods } from './modules/skills.methods.mjs';
|
|
|
2072
3774
|
? `工作区文件已保存${this.agentsWorkspaceFileName ? `: ${this.agentsWorkspaceFileName}` : ''}`
|
|
2073
3775
|
: (this.agentsContext === 'openclaw' ? 'OpenClaw AGENTS.md 已保存' : 'AGENTS.md 已保存');
|
|
2074
3776
|
this.showMessage(successLabel, 'success');
|
|
2075
|
-
this.closeAgentsModal();
|
|
3777
|
+
this.closeAgentsModal({ force: true });
|
|
2076
3778
|
} catch (e) {
|
|
2077
3779
|
this.showMessage('保存失败', 'error');
|
|
2078
3780
|
} finally {
|
|
@@ -2341,12 +4043,7 @@ import { createSkillsMethods } from './modules/skills.methods.mjs';
|
|
|
2341
4043
|
}
|
|
2342
4044
|
const existing = this.claudeConfigs[name] || {};
|
|
2343
4045
|
this.currentClaudeModel = model;
|
|
2344
|
-
this.claudeConfigs[name] = {
|
|
2345
|
-
apiKey: existing.apiKey || '',
|
|
2346
|
-
baseUrl: existing.baseUrl || '',
|
|
2347
|
-
model: model,
|
|
2348
|
-
hasKey: !!existing.apiKey
|
|
2349
|
-
};
|
|
4046
|
+
this.claudeConfigs[name] = this.mergeClaudeConfig(existing, { model });
|
|
2350
4047
|
this.saveClaudeConfigs();
|
|
2351
4048
|
this.updateClaudeModelsCurrent();
|
|
2352
4049
|
if (!this.claudeConfigs[name].apiKey) {
|
|
@@ -2373,12 +4070,7 @@ import { createSkillsMethods } from './modules/skills.methods.mjs';
|
|
|
2373
4070
|
|
|
2374
4071
|
updateConfig() {
|
|
2375
4072
|
const name = this.editingConfig.name;
|
|
2376
|
-
this.claudeConfigs[name] =
|
|
2377
|
-
apiKey: this.editingConfig.apiKey,
|
|
2378
|
-
baseUrl: this.editingConfig.baseUrl,
|
|
2379
|
-
model: this.editingConfig.model,
|
|
2380
|
-
hasKey: !!this.editingConfig.apiKey
|
|
2381
|
-
};
|
|
4073
|
+
this.claudeConfigs[name] = this.mergeClaudeConfig(this.claudeConfigs[name], this.editingConfig);
|
|
2382
4074
|
this.saveClaudeConfigs();
|
|
2383
4075
|
this.showMessage('操作成功', 'success');
|
|
2384
4076
|
this.closeEditConfigModal();
|
|
@@ -2394,12 +4086,7 @@ import { createSkillsMethods } from './modules/skills.methods.mjs';
|
|
|
2394
4086
|
|
|
2395
4087
|
async saveAndApplyConfig() {
|
|
2396
4088
|
const name = this.editingConfig.name;
|
|
2397
|
-
this.claudeConfigs[name] =
|
|
2398
|
-
apiKey: this.editingConfig.apiKey,
|
|
2399
|
-
baseUrl: this.editingConfig.baseUrl,
|
|
2400
|
-
model: this.editingConfig.model,
|
|
2401
|
-
hasKey: !!this.editingConfig.apiKey
|
|
2402
|
-
};
|
|
4089
|
+
this.claudeConfigs[name] = this.mergeClaudeConfig(this.claudeConfigs[name], this.editingConfig);
|
|
2403
4090
|
this.saveClaudeConfigs();
|
|
2404
4091
|
|
|
2405
4092
|
const config = this.claudeConfigs[name];
|
|
@@ -2438,12 +4125,7 @@ import { createSkillsMethods } from './modules/skills.methods.mjs';
|
|
|
2438
4125
|
return this.showMessage('配置已存在', 'info');
|
|
2439
4126
|
}
|
|
2440
4127
|
|
|
2441
|
-
this.claudeConfigs[name] = {
|
|
2442
|
-
apiKey: this.newClaudeConfig.apiKey,
|
|
2443
|
-
baseUrl: this.newClaudeConfig.baseUrl,
|
|
2444
|
-
model: this.newClaudeConfig.model,
|
|
2445
|
-
hasKey: !!this.newClaudeConfig.apiKey
|
|
2446
|
-
};
|
|
4128
|
+
this.claudeConfigs[name] = this.mergeClaudeConfig({}, this.newClaudeConfig);
|
|
2447
4129
|
|
|
2448
4130
|
this.currentClaudeConfig = name;
|
|
2449
4131
|
this.saveClaudeConfigs();
|
|
@@ -2452,12 +4134,18 @@ import { createSkillsMethods } from './modules/skills.methods.mjs';
|
|
|
2452
4134
|
this.refreshClaudeModelContext();
|
|
2453
4135
|
},
|
|
2454
4136
|
|
|
2455
|
-
deleteClaudeConfig(name) {
|
|
4137
|
+
async deleteClaudeConfig(name) {
|
|
2456
4138
|
if (Object.keys(this.claudeConfigs).length <= 1) {
|
|
2457
4139
|
return this.showMessage('至少保留一项', 'error');
|
|
2458
4140
|
}
|
|
2459
|
-
|
|
2460
|
-
|
|
4141
|
+
const confirmed = await this.requestConfirmDialog({
|
|
4142
|
+
title: '删除 Claude 配置',
|
|
4143
|
+
message: `确定删除配置 "${name}"?`,
|
|
4144
|
+
confirmText: '删除',
|
|
4145
|
+
cancelText: '取消',
|
|
4146
|
+
danger: true
|
|
4147
|
+
});
|
|
4148
|
+
if (!confirmed) return;
|
|
2461
4149
|
|
|
2462
4150
|
delete this.claudeConfigs[name];
|
|
2463
4151
|
if (this.currentClaudeConfig === name) {
|
|
@@ -2474,6 +4162,9 @@ import { createSkillsMethods } from './modules/skills.methods.mjs';
|
|
|
2474
4162
|
const config = this.claudeConfigs[name];
|
|
2475
4163
|
|
|
2476
4164
|
if (!config.apiKey) {
|
|
4165
|
+
if (config.externalCredentialType) {
|
|
4166
|
+
return this.showMessage('检测到外部 Claude 认证状态;当前仅支持展示,若需由 codexmate 接管请补充 API Key', 'info');
|
|
4167
|
+
}
|
|
2477
4168
|
return this.showMessage('请先配置 API Key', 'error');
|
|
2478
4169
|
}
|
|
2479
4170
|
|
|
@@ -3540,11 +5231,18 @@ import { createSkillsMethods } from './modules/skills.methods.mjs';
|
|
|
3540
5231
|
}
|
|
3541
5232
|
},
|
|
3542
5233
|
|
|
3543
|
-
deleteOpenclawConfig(name) {
|
|
5234
|
+
async deleteOpenclawConfig(name) {
|
|
3544
5235
|
if (Object.keys(this.openclawConfigs).length <= 1) {
|
|
3545
5236
|
return this.showMessage('至少保留一项', 'error');
|
|
3546
5237
|
}
|
|
3547
|
-
|
|
5238
|
+
const confirmed = await this.requestConfirmDialog({
|
|
5239
|
+
title: '删除 OpenClaw 配置',
|
|
5240
|
+
message: `确定删除配置 "${name}"?`,
|
|
5241
|
+
confirmText: '删除',
|
|
5242
|
+
cancelText: '取消',
|
|
5243
|
+
danger: true
|
|
5244
|
+
});
|
|
5245
|
+
if (!confirmed) return;
|
|
3548
5246
|
delete this.openclawConfigs[name];
|
|
3549
5247
|
if (this.currentOpenclawConfig === name) {
|
|
3550
5248
|
this.currentOpenclawConfig = Object.keys(this.openclawConfigs)[0];
|
|
@@ -4070,4 +5768,5 @@ import { createSkillsMethods } from './modules/skills.methods.mjs';
|
|
|
4070
5768
|
|
|
4071
5769
|
app.mount('#app');
|
|
4072
5770
|
});
|
|
4073
|
-
|
|
5771
|
+
|
|
5772
|
+
|