codexmate 0.0.17 → 0.0.18

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,350 @@
1
+ import { buildSessionListParams } from './logic.mjs';
2
+
3
+ function clearSessionTimelineRefs(vm) {
4
+ if (typeof vm.clearSessionTimelineRefs === 'function') {
5
+ vm.clearSessionTimelineRefs();
6
+ return;
7
+ }
8
+ vm.sessionMessageRefMap = Object.create(null);
9
+ if (vm && typeof vm === 'object' && Object.prototype.hasOwnProperty.call(vm, 'sessionMessageRefBinderMap')) {
10
+ vm.sessionMessageRefBinderMap = Object.create(null);
11
+ }
12
+ }
13
+
14
+ export function switchMainTab(tab) {
15
+ const nextTab = typeof tab === 'string' ? tab : '';
16
+ const previousTab = this.mainTab;
17
+ const leavingSessions = previousTab === 'sessions' && nextTab !== 'sessions';
18
+ this.mainTab = nextTab;
19
+
20
+ if (leavingSessions) {
21
+ const teardown = () => {
22
+ if (this.mainTab === 'sessions') return;
23
+ if (typeof this.finalizeSessionTabTeardown === 'function') {
24
+ if (typeof this.suspendSessionTabRender === 'function') {
25
+ this.suspendSessionTabRender();
26
+ }
27
+ this.finalizeSessionTabTeardown();
28
+ return;
29
+ }
30
+ if (typeof this.teardownSessionTabRender === 'function') {
31
+ this.teardownSessionTabRender();
32
+ }
33
+ };
34
+ if (typeof this.scheduleSessionTabDeferredTeardown === 'function') {
35
+ this.scheduleSessionTabDeferredTeardown(teardown);
36
+ } else if (typeof this.scheduleAfterFrame === 'function') {
37
+ this.scheduleAfterFrame(teardown);
38
+ } else {
39
+ teardown();
40
+ }
41
+ }
42
+
43
+ if (nextTab === 'sessions' && !this.sessionsLoadedOnce) {
44
+ this.loadSessions();
45
+ }
46
+ if (nextTab === 'sessions') {
47
+ this.prepareSessionTabRender();
48
+ }
49
+ const shouldLoadTrashListOnSettingsEnter = nextTab === 'settings'
50
+ && this.settingsTab === 'trash'
51
+ && typeof this.loadSessionTrash === 'function';
52
+ if (shouldLoadTrashListOnSettingsEnter) {
53
+ this.loadSessionTrash({
54
+ forceRefresh: !!this.sessionTrashLoadedOnce
55
+ });
56
+ }
57
+ const shouldPrimeTrashCountOnSettingsEnter = nextTab === 'settings'
58
+ && this.settingsTab !== 'trash'
59
+ && typeof this.loadSessionTrashCount === 'function';
60
+ if (shouldPrimeTrashCountOnSettingsEnter) {
61
+ this.sessionTrashLoadedOnce = false;
62
+ this.loadSessionTrashCount({ silent: true });
63
+ }
64
+ if (nextTab === 'config' && this.configMode === 'claude') {
65
+ const expectedTab = nextTab;
66
+ const expectedConfigMode = this.configMode;
67
+ const refresh = () => {
68
+ if (this.mainTab !== expectedTab || this.configMode !== expectedConfigMode) return;
69
+ this.refreshClaudeModelContext();
70
+ };
71
+ if (typeof this.scheduleAfterFrame === 'function') {
72
+ this.scheduleAfterFrame(refresh);
73
+ } else {
74
+ refresh();
75
+ }
76
+ }
77
+ }
78
+
79
+ export async function loadSessions(api) {
80
+ if (this.sessionsLoading) return;
81
+ this.sessionsLoading = true;
82
+ this.activeSessionDetailError = '';
83
+ let loadSucceeded = false;
84
+ const params = buildSessionListParams({
85
+ source: this.sessionFilterSource,
86
+ pathFilter: this.sessionPathFilter,
87
+ query: this.sessionQuery,
88
+ roleFilter: this.sessionRoleFilter,
89
+ timeRangePreset: this.sessionTimePreset
90
+ });
91
+ try {
92
+ const res = await api('list-sessions', params);
93
+ if (res.error) {
94
+ this.showMessage(res.error, 'error');
95
+ this.sessionsList = [];
96
+ this.activeSession = null;
97
+ this.activeSessionMessages = [];
98
+ this.resetSessionDetailPagination();
99
+ this.resetSessionPreviewMessageRender();
100
+ this.activeSessionDetailClipped = false;
101
+ this.cancelSessionTimelineSync();
102
+ this.sessionTimelineActiveKey = '';
103
+ clearSessionTimelineRefs(this);
104
+ } else {
105
+ loadSucceeded = true;
106
+ this.sessionsList = Array.isArray(res.sessions) ? res.sessions : [];
107
+ this.syncSessionPathOptionsForSource(
108
+ this.sessionFilterSource,
109
+ this.extractPathOptionsFromSessions(this.sessionsList),
110
+ true
111
+ );
112
+ if (this.sessionsList.length === 0) {
113
+ this.activeSession = null;
114
+ this.activeSessionMessages = [];
115
+ this.resetSessionDetailPagination();
116
+ this.resetSessionPreviewMessageRender();
117
+ this.activeSessionDetailClipped = false;
118
+ this.cancelSessionTimelineSync();
119
+ this.sessionTimelineActiveKey = '';
120
+ clearSessionTimelineRefs(this);
121
+ } else {
122
+ const oldKey = this.activeSession ? this.getSessionExportKey(this.activeSession) : '';
123
+ const matched = this.sessionsList.find(item => this.getSessionExportKey(item) === oldKey);
124
+ this.activeSession = matched || this.sessionsList[0];
125
+ this.activeSessionMessages = [];
126
+ this.resetSessionDetailPagination();
127
+ this.resetSessionPreviewMessageRender();
128
+ this.activeSessionDetailError = '';
129
+ this.activeSessionDetailClipped = false;
130
+ this.cancelSessionTimelineSync();
131
+ this.sessionTimelineActiveKey = '';
132
+ clearSessionTimelineRefs(this);
133
+ await this.loadActiveSessionDetail();
134
+ }
135
+ void this.loadSessionPathOptions({ source: this.sessionFilterSource });
136
+ }
137
+ } catch (e) {
138
+ this.sessionsList = [];
139
+ this.activeSession = null;
140
+ this.activeSessionMessages = [];
141
+ this.resetSessionDetailPagination();
142
+ this.resetSessionPreviewMessageRender();
143
+ this.activeSessionDetailClipped = false;
144
+ this.cancelSessionTimelineSync();
145
+ this.sessionTimelineActiveKey = '';
146
+ clearSessionTimelineRefs(this);
147
+ this.showMessage('加载会话失败', 'error');
148
+ } finally {
149
+ this.sessionsLoading = false;
150
+ if (loadSucceeded) {
151
+ this.sessionsLoadedOnce = true;
152
+ }
153
+ }
154
+ }
155
+
156
+ export async function loadActiveSessionDetail(api, options = {}) {
157
+ if (!this.activeSession) {
158
+ this.activeSessionMessages = [];
159
+ this.resetSessionDetailPagination();
160
+ this.resetSessionPreviewMessageRender();
161
+ this.activeSessionDetailError = '';
162
+ this.activeSessionDetailClipped = false;
163
+ this.cancelSessionTimelineSync();
164
+ this.sessionTimelineActiveKey = '';
165
+ clearSessionTimelineRefs(this);
166
+ return;
167
+ }
168
+
169
+ const currentActiveSession = this.activeSession;
170
+ const requestSeq = ++this.sessionDetailRequestSeq;
171
+ this.sessionDetailLoading = true;
172
+ this.activeSessionDetailError = '';
173
+ const fallbackLimit = Number.isFinite(this.sessionDetailInitialMessageLimit)
174
+ ? Math.max(1, Math.floor(this.sessionDetailInitialMessageLimit))
175
+ : 80;
176
+ const rawLimit = Number(this.sessionDetailMessageLimit);
177
+ const messageLimit = Number.isFinite(rawLimit)
178
+ ? Math.max(1, Math.floor(rawLimit))
179
+ : fallbackLimit;
180
+ try {
181
+ const res = await api('session-detail', {
182
+ source: this.activeSession.source,
183
+ sessionId: this.activeSession.sessionId,
184
+ filePath: this.activeSession.filePath,
185
+ messageLimit
186
+ });
187
+
188
+ if (requestSeq !== this.sessionDetailRequestSeq) {
189
+ return;
190
+ }
191
+ if (!this.activeSession || this.activeSession !== currentActiveSession) {
192
+ return;
193
+ }
194
+
195
+ if (res.error) {
196
+ this.activeSessionMessages = [];
197
+ this.resetSessionPreviewMessageRender();
198
+ this.activeSessionDetailClipped = false;
199
+ this.activeSessionDetailError = res.error;
200
+ this.cancelSessionTimelineSync();
201
+ this.sessionTimelineActiveKey = '';
202
+ clearSessionTimelineRefs(this);
203
+ return;
204
+ }
205
+
206
+ const rawMessages = Array.isArray(res.messages) ? res.messages : [];
207
+ const normalizedMessages = rawMessages.map((message) => Object.freeze(this.normalizeSessionMessage(message)));
208
+ this.activeSessionMessages = Object.freeze(normalizedMessages);
209
+ if (typeof this.invalidateSessionTimelineMeasurementCache === 'function') {
210
+ this.invalidateSessionTimelineMeasurementCache(true);
211
+ }
212
+ this.activeSessionDetailClipped = !!res.clipped;
213
+ const responseLimitRaw = Number(res.messageLimit);
214
+ this.sessionDetailMessageLimit = Number.isFinite(responseLimitRaw)
215
+ ? Math.max(1, Math.floor(responseLimitRaw))
216
+ : messageLimit;
217
+ if (res.sourceLabel) {
218
+ this.activeSession.sourceLabel = res.sourceLabel;
219
+ }
220
+ if (res.sessionId) {
221
+ this.activeSession.sessionId = res.sessionId;
222
+ if (!this.activeSession.title) {
223
+ this.activeSession.title = res.sessionId;
224
+ }
225
+ }
226
+ if (res.filePath) {
227
+ this.activeSession.filePath = res.filePath;
228
+ }
229
+ if (res.updatedAt) {
230
+ this.activeSession.updatedAt = res.updatedAt;
231
+ }
232
+ if (res.cwd) {
233
+ this.activeSession.cwd = res.cwd;
234
+ }
235
+ if (Number.isFinite(res.totalMessages)) {
236
+ this.syncActiveSessionMessageCount(res.totalMessages);
237
+ }
238
+ if (this.mainTab === 'sessions' && this.sessionPreviewRenderEnabled) {
239
+ const preserveVisibleCount = !!options.preserveVisibleCount;
240
+ const pendingVisibleRaw = Number(this.sessionPreviewPendingVisibleCount);
241
+ const pendingVisible = Number.isFinite(pendingVisibleRaw)
242
+ ? Math.max(0, Math.floor(pendingVisibleRaw))
243
+ : 0;
244
+ if (preserveVisibleCount && pendingVisible > 0) {
245
+ this.sessionPreviewVisibleCount = Math.min(pendingVisible, this.activeSessionMessages.length);
246
+ } else {
247
+ this.primeSessionPreviewMessageRender();
248
+ }
249
+ }
250
+ this.sessionPreviewPendingVisibleCount = 0;
251
+ this.$nextTick(() => {
252
+ if (this.mainTab !== 'sessions' || !this.sessionPreviewRenderEnabled) {
253
+ return;
254
+ }
255
+ this.updateSessionTimelineOffset();
256
+ if (this.sessionTimelineEnabled) {
257
+ this.scheduleSessionTimelineSync();
258
+ }
259
+ });
260
+ } catch (e) {
261
+ if (requestSeq !== this.sessionDetailRequestSeq) {
262
+ return;
263
+ }
264
+ if (!this.activeSession || this.activeSession !== currentActiveSession) {
265
+ return;
266
+ }
267
+ this.activeSessionMessages = [];
268
+ this.sessionPreviewPendingVisibleCount = 0;
269
+ this.resetSessionPreviewMessageRender();
270
+ this.activeSessionDetailClipped = false;
271
+ this.activeSessionDetailError = '加载会话内容失败: ' + e.message;
272
+ this.cancelSessionTimelineSync();
273
+ this.sessionTimelineActiveKey = '';
274
+ clearSessionTimelineRefs(this);
275
+ } finally {
276
+ if (requestSeq === this.sessionDetailRequestSeq) {
277
+ this.sessionDetailLoading = false;
278
+ }
279
+ }
280
+ }
281
+
282
+ export async function loadMoreSessionMessages(stepSize) {
283
+ if (this.mainTab !== 'sessions' || !this.sessionPreviewRenderEnabled) {
284
+ return;
285
+ }
286
+ const total = Array.isArray(this.activeSessionMessages)
287
+ ? this.activeSessionMessages.length
288
+ : 0;
289
+ if (total <= 0) {
290
+ this.sessionPreviewVisibleCount = 0;
291
+ return;
292
+ }
293
+ const step = Number.isFinite(stepSize)
294
+ ? Math.max(1, Math.floor(stepSize))
295
+ : (Number.isFinite(this.sessionPreviewLoadStep)
296
+ ? Math.max(1, Math.floor(this.sessionPreviewLoadStep))
297
+ : 40);
298
+ const current = Number.isFinite(this.sessionPreviewVisibleCount)
299
+ ? Math.max(0, Math.floor(this.sessionPreviewVisibleCount))
300
+ : 0;
301
+ const targetVisible = current + step;
302
+ if (targetVisible <= total) {
303
+ this.sessionPreviewVisibleCount = Math.min(total, targetVisible);
304
+ return;
305
+ }
306
+
307
+ this.sessionPreviewVisibleCount = total;
308
+ if (this.sessionDetailLoading) {
309
+ return;
310
+ }
311
+
312
+ const totalKnownRaw = Number(this.activeSession && this.activeSession.messageCount);
313
+ const totalKnown = Number.isFinite(totalKnownRaw)
314
+ ? Math.max(0, Math.floor(totalKnownRaw))
315
+ : 0;
316
+ const hasMoreOnDisk = this.activeSessionDetailClipped || (totalKnown > total);
317
+ if (!hasMoreOnDisk) {
318
+ return;
319
+ }
320
+
321
+ const currentLimitRaw = Number(this.sessionDetailMessageLimit);
322
+ const currentLimit = Number.isFinite(currentLimitRaw)
323
+ ? Math.max(1, Math.floor(currentLimitRaw))
324
+ : Math.max(1, total);
325
+ const fetchStep = Number.isFinite(this.sessionDetailFetchStep)
326
+ ? Math.max(1, Math.floor(this.sessionDetailFetchStep))
327
+ : 80;
328
+ const limitCapRaw = Number(this.sessionDetailMessageLimitCap);
329
+ const limitCap = Number.isFinite(limitCapRaw)
330
+ ? Math.max(1, Math.floor(limitCapRaw))
331
+ : 1000;
332
+
333
+ let nextLimit = Math.max(currentLimit + fetchStep, targetVisible);
334
+ if (totalKnown > 0) {
335
+ nextLimit = Math.min(nextLimit, totalKnown);
336
+ }
337
+ nextLimit = Math.min(nextLimit, limitCap);
338
+ if (nextLimit <= currentLimit) {
339
+ return;
340
+ }
341
+
342
+ this.sessionPreviewPendingVisibleCount = targetVisible;
343
+ this.sessionDetailMessageLimit = nextLimit;
344
+ this.sessionPreviewLoadingMore = true;
345
+ try {
346
+ await this.loadActiveSessionDetail({ preserveVisibleCount: true });
347
+ } finally {
348
+ this.sessionPreviewLoadingMore = false;
349
+ }
350
+ }