codexmate 0.0.25 → 0.0.27
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.md +11 -3
- package/README.zh.md +10 -2
- package/cli/builtin-proxy.js +315 -95
- package/cli/openai-bridge.js +99 -5
- package/cli/session-convert-args.js +65 -0
- package/cli/session-convert-io.js +82 -0
- package/cli/session-convert.js +43 -0
- package/cli.js +547 -32
- package/package.json +74 -74
- package/web-ui/app.js +24 -2
- package/web-ui/logic.session-convert.mjs +70 -0
- package/web-ui/logic.sessions.mjs +151 -0
- package/web-ui/modules/app.computed.dashboard.mjs +44 -1
- package/web-ui/modules/app.computed.session.mjs +336 -12
- package/web-ui/modules/app.methods.claude-config.mjs +11 -1
- package/web-ui/modules/app.methods.codex-config.mjs +76 -0
- package/web-ui/modules/app.methods.navigation.mjs +51 -3
- package/web-ui/modules/app.methods.session-actions.mjs +55 -3
- package/web-ui/modules/app.methods.session-browser.mjs +270 -3
- package/web-ui/modules/app.methods.session-timeline.mjs +34 -3
- package/web-ui/modules/app.methods.session-trash.mjs +16 -1
- package/web-ui/modules/app.methods.startup-claude.mjs +234 -125
- package/web-ui/modules/i18n.dict.mjs +76 -0
- package/web-ui/partials/index/panel-config-claude.html +12 -4
- package/web-ui/partials/index/panel-sessions.html +33 -10
- package/web-ui/partials/index/panel-settings.html +16 -0
- package/web-ui/partials/index/panel-usage.html +95 -85
- package/web-ui/session-helpers.mjs +3 -0
- package/web-ui/styles/base-theme.css +29 -25
- package/web-ui/styles/layout-shell.css +1 -1
- package/web-ui/styles/navigation-panels.css +9 -9
- package/web-ui/styles/sessions-list.css +17 -0
- package/web-ui/styles/sessions-toolbar-trash.css +62 -0
- package/web-ui/styles/sessions-usage.css +211 -83
- package/web-ui/styles/settings-panel.css +19 -0
|
@@ -10,6 +10,24 @@ export function createSessionActionMethods(options = {}) {
|
|
|
10
10
|
} = options;
|
|
11
11
|
|
|
12
12
|
return {
|
|
13
|
+
isDerivedSessionId(value) {
|
|
14
|
+
const sessionId = typeof value === 'string' ? value.trim() : String(value || '');
|
|
15
|
+
if (!sessionId) return false;
|
|
16
|
+
return /-\d{8}-\d{6}-[0-9a-f]{6}$/i.test(sessionId);
|
|
17
|
+
},
|
|
18
|
+
|
|
19
|
+
isDerivedSession(session) {
|
|
20
|
+
if (!session || typeof session !== 'object') return false;
|
|
21
|
+
if (session.derived === true) return true;
|
|
22
|
+
if (this.isDerivedSessionId(session.sessionId)) return true;
|
|
23
|
+
const rawFilePath = typeof session.filePath === 'string' ? session.filePath.trim() : '';
|
|
24
|
+
if (!rawFilePath) return false;
|
|
25
|
+
const normalized = rawFilePath.replace(/\\/g, '/');
|
|
26
|
+
if (normalized.includes('/.codexmate/sessions/derived/')) return true;
|
|
27
|
+
if (normalized.includes('/codexmate-derived/')) return true;
|
|
28
|
+
return false;
|
|
29
|
+
},
|
|
30
|
+
|
|
13
31
|
getSessionStandaloneContext() {
|
|
14
32
|
try {
|
|
15
33
|
const url = new URL(window.location.href);
|
|
@@ -20,6 +38,8 @@ export function createSessionActionMethods(options = {}) {
|
|
|
20
38
|
const source = (url.searchParams.get('source') || '').trim().toLowerCase();
|
|
21
39
|
const sessionId = (url.searchParams.get('sessionId') || url.searchParams.get('id') || '').trim();
|
|
22
40
|
const filePath = (url.searchParams.get('filePath') || url.searchParams.get('path') || '').trim();
|
|
41
|
+
const maxMessagesRaw = (url.searchParams.get('maxMessages') || '').trim();
|
|
42
|
+
const maxMessages = Number(maxMessagesRaw);
|
|
23
43
|
let error = '';
|
|
24
44
|
if (!source) {
|
|
25
45
|
error = '缺少 source 参数';
|
|
@@ -39,7 +59,8 @@ export function createSessionActionMethods(options = {}) {
|
|
|
39
59
|
params: {
|
|
40
60
|
source,
|
|
41
61
|
sessionId,
|
|
42
|
-
filePath
|
|
62
|
+
filePath,
|
|
63
|
+
maxMessages: Number.isFinite(maxMessages) && maxMessages > 0 ? Math.floor(maxMessages) : 0
|
|
43
64
|
},
|
|
44
65
|
error: ''
|
|
45
66
|
};
|
|
@@ -67,7 +88,8 @@ export function createSessionActionMethods(options = {}) {
|
|
|
67
88
|
sourceLabel,
|
|
68
89
|
sessionId: context.params.sessionId,
|
|
69
90
|
filePath: context.params.filePath,
|
|
70
|
-
title: context.params.sessionId || context.params.filePath || '会话'
|
|
91
|
+
title: context.params.sessionId || context.params.filePath || '会话',
|
|
92
|
+
maxMessages: context.params.maxMessages || 50
|
|
71
93
|
};
|
|
72
94
|
this.activeSessionMessages = [];
|
|
73
95
|
this.activeSessionDetailError = '';
|
|
@@ -117,6 +139,13 @@ export function createSessionActionMethods(options = {}) {
|
|
|
117
139
|
if (!session) return false;
|
|
118
140
|
const source = String(session.source || '').trim().toLowerCase();
|
|
119
141
|
const sessionId = typeof session.sessionId === 'string' ? session.sessionId.trim() : '';
|
|
142
|
+
const filePath = typeof session.filePath === 'string' ? session.filePath.trim() : '';
|
|
143
|
+
if (source === 'claude') {
|
|
144
|
+
return !!sessionId || !!this.extractClaudeResumeKeyFromFilePath(filePath);
|
|
145
|
+
}
|
|
146
|
+
if (source === 'gemini') {
|
|
147
|
+
return !!sessionId || !!this.extractClaudeResumeKeyFromFilePath(filePath);
|
|
148
|
+
}
|
|
120
149
|
return (source === 'codex' || source === 'codebuddy' || source === 'gemini') && !!sessionId;
|
|
121
150
|
},
|
|
122
151
|
|
|
@@ -140,19 +169,42 @@ export function createSessionActionMethods(options = {}) {
|
|
|
140
169
|
buildResumeCommand(session) {
|
|
141
170
|
const source = session && session.source ? String(session.source).trim().toLowerCase() : '';
|
|
142
171
|
const sessionId = session && session.sessionId ? String(session.sessionId).trim() : '';
|
|
143
|
-
const
|
|
172
|
+
const filePath = session && session.filePath ? String(session.filePath).trim() : '';
|
|
173
|
+
const resumeKey = (source === 'claude' || source === 'gemini')
|
|
174
|
+
? (sessionId || this.extractClaudeResumeKeyFromFilePath(filePath))
|
|
175
|
+
: sessionId;
|
|
176
|
+
const arg = this.quoteResumeArg(resumeKey);
|
|
144
177
|
if (source === 'codebuddy') {
|
|
145
178
|
return `codebuddy -r ${arg}`;
|
|
146
179
|
}
|
|
147
180
|
if (source === 'gemini') {
|
|
148
181
|
return `gemini -r ${arg}`;
|
|
149
182
|
}
|
|
183
|
+
if (source === 'claude') {
|
|
184
|
+
return `claude -r ${arg}`;
|
|
185
|
+
}
|
|
150
186
|
if (this.sessionResumeWithYolo) {
|
|
151
187
|
return `codex --yolo resume ${arg}`;
|
|
152
188
|
}
|
|
153
189
|
return `codex resume ${arg}`;
|
|
154
190
|
},
|
|
155
191
|
|
|
192
|
+
extractClaudeResumeKeyFromFilePath(filePath) {
|
|
193
|
+
const value = typeof filePath === 'string' ? filePath.trim() : '';
|
|
194
|
+
if (!value) return '';
|
|
195
|
+
const normalized = value.replace(/\\/g, '/');
|
|
196
|
+
const base = normalized.split('/').pop() || '';
|
|
197
|
+
if (!base) return '';
|
|
198
|
+
const lower = base.toLowerCase();
|
|
199
|
+
if (lower.endsWith('.jsonl')) {
|
|
200
|
+
return base.slice(0, -6);
|
|
201
|
+
}
|
|
202
|
+
if (lower.endsWith('.json')) {
|
|
203
|
+
return base.slice(0, -5);
|
|
204
|
+
}
|
|
205
|
+
return base;
|
|
206
|
+
},
|
|
207
|
+
|
|
156
208
|
quoteShellArg(value) {
|
|
157
209
|
const text = typeof value === 'string' ? value : String(value || '');
|
|
158
210
|
if (!text) return "''";
|
|
@@ -254,10 +254,25 @@ export function createSessionBrowserMethods(options = {}) {
|
|
|
254
254
|
localStorage.setItem('codexmateSessionResumeYolo', value);
|
|
255
255
|
},
|
|
256
256
|
|
|
257
|
+
normalizeSessionSortMode(value) {
|
|
258
|
+
const normalized = typeof value === 'string' ? value.trim().toLowerCase() : '';
|
|
259
|
+
return normalized === 'hot' ? 'hot' : 'time';
|
|
260
|
+
},
|
|
261
|
+
|
|
257
262
|
restoreSessionFilterCache() {
|
|
258
263
|
const urlState = readSessionsFilterUrlState();
|
|
264
|
+
const normalizeSortMode = typeof this.normalizeSessionSortMode === 'function'
|
|
265
|
+
? this.normalizeSessionSortMode.bind(this)
|
|
266
|
+
: ((value) => {
|
|
267
|
+
const normalized = typeof value === 'string' ? value.trim().toLowerCase() : '';
|
|
268
|
+
return normalized === 'hot' ? 'hot' : 'time';
|
|
269
|
+
});
|
|
259
270
|
if (urlState) {
|
|
260
271
|
applySessionsFilterUrlState(this, urlState);
|
|
272
|
+
try {
|
|
273
|
+
const sortCache = localStorage.getItem('codexmateSessionSortMode');
|
|
274
|
+
this.sessionSortMode = normalizeSortMode(sortCache);
|
|
275
|
+
} catch (_) {}
|
|
261
276
|
if (this.mainTab === 'sessions' && typeof this.loadSessions === 'function') {
|
|
262
277
|
void this.loadSessions();
|
|
263
278
|
}
|
|
@@ -271,9 +286,11 @@ export function createSessionBrowserMethods(options = {}) {
|
|
|
271
286
|
const queryCache = localStorage.getItem('codexmateSessionQuery');
|
|
272
287
|
const roleCache = localStorage.getItem('codexmateSessionRoleFilter');
|
|
273
288
|
const timeCache = localStorage.getItem('codexmateSessionTimePreset');
|
|
289
|
+
const sortCache = localStorage.getItem('codexmateSessionSortMode');
|
|
274
290
|
this.sessionQuery = typeof queryCache === 'string' ? queryCache : '';
|
|
275
291
|
this.sessionRoleFilter = normalizeSessionRoleFilter(roleCache);
|
|
276
292
|
this.sessionTimePreset = normalizeSessionTimePreset(timeCache);
|
|
293
|
+
this.sessionSortMode = normalizeSortMode(sortCache);
|
|
277
294
|
this.refreshSessionPathOptions(this.sessionFilterSource);
|
|
278
295
|
if (this.mainTab === 'sessions' && typeof this.loadSessions === 'function') {
|
|
279
296
|
const shouldReload = cached.source !== 'all'
|
|
@@ -304,6 +321,29 @@ export function createSessionBrowserMethods(options = {}) {
|
|
|
304
321
|
localStorage.setItem('codexmateSessionTimePreset', normalizeSessionTimePreset(this.sessionTimePreset));
|
|
305
322
|
},
|
|
306
323
|
|
|
324
|
+
onSessionSortChange() {
|
|
325
|
+
const normalized = this.normalizeSessionSortMode(this.sessionSortMode);
|
|
326
|
+
this.sessionSortMode = normalized;
|
|
327
|
+
try {
|
|
328
|
+
localStorage.setItem('codexmateSessionSortMode', normalized);
|
|
329
|
+
} catch (_) {}
|
|
330
|
+
},
|
|
331
|
+
|
|
332
|
+
getSessionHotLabel(session) {
|
|
333
|
+
if (!session || typeof session !== 'object') return '';
|
|
334
|
+
const updatedAtMs = Date.parse(session.updatedAt || '');
|
|
335
|
+
if (!Number.isFinite(updatedAtMs)) return '';
|
|
336
|
+
const ageMs = Date.now() - updatedAtMs;
|
|
337
|
+
if (!Number.isFinite(ageMs) || ageMs < 0) return '';
|
|
338
|
+
const messageCount = Number.isFinite(Number(session.messageCount))
|
|
339
|
+
? Math.max(0, Math.floor(Number(session.messageCount)))
|
|
340
|
+
: 0;
|
|
341
|
+
if (ageMs <= (24 * 60 * 60 * 1000) && messageCount >= 20) {
|
|
342
|
+
return typeof this.t === 'function' ? this.t('sessions.sort.hotBadge') : '热';
|
|
343
|
+
}
|
|
344
|
+
return '';
|
|
345
|
+
},
|
|
346
|
+
|
|
307
347
|
normalizeSessionPinnedMap(raw) {
|
|
308
348
|
if (!raw || typeof raw !== 'object' || Array.isArray(raw)) {
|
|
309
349
|
return {};
|
|
@@ -455,6 +495,64 @@ export function createSessionBrowserMethods(options = {}) {
|
|
|
455
495
|
await this.loadSessions();
|
|
456
496
|
},
|
|
457
497
|
|
|
498
|
+
hasActiveSessionFilters() {
|
|
499
|
+
if (this.sessionFilterSource && this.sessionFilterSource !== 'all') return true;
|
|
500
|
+
if (this.sessionPathFilter) return true;
|
|
501
|
+
if (this.sessionQuery && isSessionQueryEnabled(this.sessionFilterSource)) return true;
|
|
502
|
+
if (this.sessionRoleFilter && this.sessionRoleFilter !== 'all') return true;
|
|
503
|
+
if (this.sessionTimePreset && this.sessionTimePreset !== 'all') return true;
|
|
504
|
+
return false;
|
|
505
|
+
},
|
|
506
|
+
|
|
507
|
+
getSessionFilterChips() {
|
|
508
|
+
const chips = [];
|
|
509
|
+
if (this.sessionFilterSource && this.sessionFilterSource !== 'all') {
|
|
510
|
+
const label = this.sessionFilterSource === 'codex'
|
|
511
|
+
? this.t('sessions.source.codex')
|
|
512
|
+
: (this.sessionFilterSource === 'claude'
|
|
513
|
+
? this.t('sessions.source.claudeCode')
|
|
514
|
+
: (this.sessionFilterSource === 'gemini'
|
|
515
|
+
? this.t('sessions.source.gemini')
|
|
516
|
+
: (this.sessionFilterSource === 'codebuddy' ? this.t('sessions.source.codebuddy') : this.sessionFilterSource)));
|
|
517
|
+
chips.push({ key: 'source', title: this.t('sessions.filters.source'), value: label });
|
|
518
|
+
}
|
|
519
|
+
if (this.sessionPathFilter) {
|
|
520
|
+
chips.push({ key: 'path', title: this.t('sessions.filters.path'), value: this.sessionPathFilter });
|
|
521
|
+
}
|
|
522
|
+
if (this.sessionQuery && isSessionQueryEnabled(this.sessionFilterSource)) {
|
|
523
|
+
chips.push({ key: 'query', title: this.t('sessions.filters.keyword'), value: this.sessionQuery });
|
|
524
|
+
}
|
|
525
|
+
if (this.sessionRoleFilter && this.sessionRoleFilter !== 'all') {
|
|
526
|
+
const label = this.sessionRoleFilter === 'user'
|
|
527
|
+
? this.t('sessions.role.user')
|
|
528
|
+
: (this.sessionRoleFilter === 'assistant'
|
|
529
|
+
? this.t('sessions.role.assistant')
|
|
530
|
+
: (this.sessionRoleFilter === 'system' ? this.t('sessions.role.system') : this.sessionRoleFilter));
|
|
531
|
+
chips.push({ key: 'role', title: this.t('sessions.filters.role'), value: label });
|
|
532
|
+
}
|
|
533
|
+
if (this.sessionTimePreset && this.sessionTimePreset !== 'all') {
|
|
534
|
+
const label = this.sessionTimePreset === '7d'
|
|
535
|
+
? this.t('sessions.time.7d')
|
|
536
|
+
: (this.sessionTimePreset === '30d'
|
|
537
|
+
? this.t('sessions.time.30d')
|
|
538
|
+
: (this.sessionTimePreset === '90d' ? this.t('sessions.time.90d') : this.sessionTimePreset));
|
|
539
|
+
chips.push({ key: 'time', title: this.t('sessions.filters.time'), value: label });
|
|
540
|
+
}
|
|
541
|
+
return chips;
|
|
542
|
+
},
|
|
543
|
+
|
|
544
|
+
async clearSessionFilterChip(key) {
|
|
545
|
+
const normalized = typeof key === 'string' ? key.trim().toLowerCase() : '';
|
|
546
|
+
if (normalized === 'source') this.sessionFilterSource = 'all';
|
|
547
|
+
if (normalized === 'path') this.sessionPathFilter = '';
|
|
548
|
+
if (normalized === 'query') this.sessionQuery = '';
|
|
549
|
+
if (normalized === 'role') this.sessionRoleFilter = 'all';
|
|
550
|
+
if (normalized === 'time') this.sessionTimePreset = 'all';
|
|
551
|
+
this.persistSessionFilterCache();
|
|
552
|
+
syncSessionsFilterUrl(this);
|
|
553
|
+
await this.loadSessions();
|
|
554
|
+
},
|
|
555
|
+
|
|
458
556
|
async clearSessionFilters() {
|
|
459
557
|
this.sessionFilterSource = 'all';
|
|
460
558
|
this.sessionPathFilter = '';
|
|
@@ -538,6 +636,135 @@ export function createSessionBrowserMethods(options = {}) {
|
|
|
538
636
|
}
|
|
539
637
|
},
|
|
540
638
|
|
|
639
|
+
cancelScheduledSessionListMessageCountHydrate() {
|
|
640
|
+
const handle = this.__sessionListMessageCountHydrateHandle || null;
|
|
641
|
+
if (!handle) return;
|
|
642
|
+
if (typeof this.cancelIdleTask === 'function') {
|
|
643
|
+
this.cancelIdleTask(handle);
|
|
644
|
+
}
|
|
645
|
+
this.__sessionListMessageCountHydrateHandle = null;
|
|
646
|
+
},
|
|
647
|
+
|
|
648
|
+
resetSessionListMessageCountHydrate() {
|
|
649
|
+
this.cancelScheduledSessionListMessageCountHydrate();
|
|
650
|
+
this.__sessionListMessageCountHydrateInFlight = false;
|
|
651
|
+
this.__sessionListMessageCountHydrateLastScheduleAt = 0;
|
|
652
|
+
this.__sessionListMessageCountHydrateLastAttemptAtMap = {};
|
|
653
|
+
this.sessionListMessageCountHydrateRequestSeq = (Number(this.sessionListMessageCountHydrateRequestSeq) || 0) + 1;
|
|
654
|
+
},
|
|
655
|
+
|
|
656
|
+
scheduleSessionListMessageCountHydrate() {
|
|
657
|
+
if (this.mainTab !== 'sessions' || !this.sessionListRenderEnabled) {
|
|
658
|
+
return;
|
|
659
|
+
}
|
|
660
|
+
const now = Date.now();
|
|
661
|
+
const lastAt = Number(this.__sessionListMessageCountHydrateLastScheduleAt || 0);
|
|
662
|
+
if ((now - lastAt) < 120) {
|
|
663
|
+
return;
|
|
664
|
+
}
|
|
665
|
+
this.__sessionListMessageCountHydrateLastScheduleAt = now;
|
|
666
|
+
this.cancelScheduledSessionListMessageCountHydrate();
|
|
667
|
+
const run = () => {
|
|
668
|
+
this.__sessionListMessageCountHydrateHandle = null;
|
|
669
|
+
void this.hydrateVisibleSessionListMessageCounts();
|
|
670
|
+
};
|
|
671
|
+
if (typeof this.scheduleIdleTask === 'function') {
|
|
672
|
+
this.__sessionListMessageCountHydrateHandle = this.scheduleIdleTask(run, 160);
|
|
673
|
+
return;
|
|
674
|
+
}
|
|
675
|
+
if (typeof this.scheduleAfterFrame === 'function') {
|
|
676
|
+
this.scheduleAfterFrame(run);
|
|
677
|
+
return;
|
|
678
|
+
}
|
|
679
|
+
run();
|
|
680
|
+
},
|
|
681
|
+
|
|
682
|
+
async hydrateVisibleSessionListMessageCounts() {
|
|
683
|
+
if (this.__sessionListMessageCountHydrateInFlight) {
|
|
684
|
+
return;
|
|
685
|
+
}
|
|
686
|
+
if (this.mainTab !== 'sessions' || !this.sessionListRenderEnabled) {
|
|
687
|
+
return;
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
const visible = Array.isArray(this.visibleSessionsList) ? this.visibleSessionsList : [];
|
|
691
|
+
if (!visible.length) return;
|
|
692
|
+
|
|
693
|
+
const now = Date.now();
|
|
694
|
+
const lastAttemptAtMap = (this.__sessionListMessageCountHydrateLastAttemptAtMap && typeof this.__sessionListMessageCountHydrateLastAttemptAtMap === 'object')
|
|
695
|
+
? this.__sessionListMessageCountHydrateLastAttemptAtMap
|
|
696
|
+
: {};
|
|
697
|
+
const targets = [];
|
|
698
|
+
for (const session of visible) {
|
|
699
|
+
if (!session || typeof session !== 'object') continue;
|
|
700
|
+
const messageCountRaw = Number(session.messageCount);
|
|
701
|
+
const shouldHydrate = !Number.isFinite(messageCountRaw) || messageCountRaw === 0;
|
|
702
|
+
if (!shouldHydrate) continue;
|
|
703
|
+
const key = this.getSessionExportKey(session);
|
|
704
|
+
if (!key) continue;
|
|
705
|
+
const lastAttempt = Number(lastAttemptAtMap[key] || 0);
|
|
706
|
+
if ((now - lastAttempt) < 5000) {
|
|
707
|
+
continue;
|
|
708
|
+
}
|
|
709
|
+
lastAttemptAtMap[key] = now;
|
|
710
|
+
targets.push({
|
|
711
|
+
source: session.source,
|
|
712
|
+
sessionId: session.sessionId,
|
|
713
|
+
filePath: session.filePath
|
|
714
|
+
});
|
|
715
|
+
if (targets.length >= 32) {
|
|
716
|
+
break;
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
this.__sessionListMessageCountHydrateLastAttemptAtMap = lastAttemptAtMap;
|
|
720
|
+
if (!targets.length) return;
|
|
721
|
+
|
|
722
|
+
const requestSeq = (Number(this.sessionListMessageCountHydrateRequestSeq) || 0) + 1;
|
|
723
|
+
this.sessionListMessageCountHydrateRequestSeq = requestSeq;
|
|
724
|
+
this.__sessionListMessageCountHydrateInFlight = true;
|
|
725
|
+
try {
|
|
726
|
+
const res = await api('session-message-counts', {
|
|
727
|
+
items: targets,
|
|
728
|
+
limit: targets.length
|
|
729
|
+
});
|
|
730
|
+
if (requestSeq !== Number(this.sessionListMessageCountHydrateRequestSeq || 0)) {
|
|
731
|
+
return;
|
|
732
|
+
}
|
|
733
|
+
if (!res || res.error || !Array.isArray(res.items)) {
|
|
734
|
+
return;
|
|
735
|
+
}
|
|
736
|
+
const byKey = new Map();
|
|
737
|
+
for (const item of res.items) {
|
|
738
|
+
if (!item || typeof item !== 'object') continue;
|
|
739
|
+
const key = typeof item.key === 'string' ? item.key : '';
|
|
740
|
+
if (!key) continue;
|
|
741
|
+
const messageCount = Number(item.messageCount);
|
|
742
|
+
if (!Number.isFinite(messageCount) || messageCount < 0) continue;
|
|
743
|
+
byKey.set(key, Math.floor(messageCount));
|
|
744
|
+
}
|
|
745
|
+
if (!byKey.size) {
|
|
746
|
+
return;
|
|
747
|
+
}
|
|
748
|
+
const sessions = Array.isArray(this.sessionsList) ? this.sessionsList : [];
|
|
749
|
+
const sessionMap = new Map(sessions.map((session) => [this.getSessionExportKey(session), session]));
|
|
750
|
+
for (const [key, count] of byKey.entries()) {
|
|
751
|
+
const matched = sessionMap.get(key);
|
|
752
|
+
if (matched) {
|
|
753
|
+
matched.messageCount = count;
|
|
754
|
+
}
|
|
755
|
+
if (this.activeSession && this.getSessionExportKey(this.activeSession) === key) {
|
|
756
|
+
this.activeSession.messageCount = count;
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
} catch (_) {
|
|
760
|
+
return;
|
|
761
|
+
} finally {
|
|
762
|
+
if (requestSeq === Number(this.sessionListMessageCountHydrateRequestSeq || 0)) {
|
|
763
|
+
this.__sessionListMessageCountHydrateInFlight = false;
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
},
|
|
767
|
+
|
|
541
768
|
invalidateSessionsUsageData(options = {}) {
|
|
542
769
|
this.sessionsUsageLoadedOnce = false;
|
|
543
770
|
this.sessionsUsageLoadedLimit = 0;
|
|
@@ -551,9 +778,35 @@ export function createSessionBrowserMethods(options = {}) {
|
|
|
551
778
|
const normalized = typeof nextRange === 'string' ? nextRange.trim().toLowerCase() : '';
|
|
552
779
|
const range = normalized === 'all' ? 'all' : (normalized === '30d' ? '30d' : '7d');
|
|
553
780
|
this.sessionsUsageTimeRange = range;
|
|
781
|
+
try { localStorage.setItem('sessionsUsageTimeRange', range); } catch (_) {}
|
|
782
|
+
if (range === 'all') {
|
|
783
|
+
this.sessionsUsageCompareEnabled = false;
|
|
784
|
+
}
|
|
554
785
|
void this.loadSessionsUsage({ range });
|
|
555
786
|
},
|
|
556
787
|
|
|
788
|
+
toggleSessionsUsageCompare() {
|
|
789
|
+
if (this.sessionsUsageTimeRange === 'all') {
|
|
790
|
+
this.sessionsUsageCompareEnabled = false;
|
|
791
|
+
return;
|
|
792
|
+
}
|
|
793
|
+
this.sessionsUsageCompareEnabled = !this.sessionsUsageCompareEnabled;
|
|
794
|
+
const range = typeof this.sessionsUsageTimeRange === 'string' ? this.sessionsUsageTimeRange.trim().toLowerCase() : '7d';
|
|
795
|
+
void this.loadSessionsUsage({
|
|
796
|
+
range,
|
|
797
|
+
limit: this.sessionsUsageCompareEnabled ? 2000 : undefined
|
|
798
|
+
});
|
|
799
|
+
},
|
|
800
|
+
|
|
801
|
+
selectSessionsUsageDay(dayKey) {
|
|
802
|
+
const normalized = typeof dayKey === 'string' ? dayKey.trim() : '';
|
|
803
|
+
this.sessionsUsageSelectedDayKey = normalized;
|
|
804
|
+
},
|
|
805
|
+
|
|
806
|
+
clearSessionsUsageDay() {
|
|
807
|
+
this.sessionsUsageSelectedDayKey = '';
|
|
808
|
+
},
|
|
809
|
+
|
|
557
810
|
async loadSessionsUsage(options = {}) {
|
|
558
811
|
if (this.sessionsUsageLoading) return;
|
|
559
812
|
const normalizedRange = typeof options.range === 'string'
|
|
@@ -562,7 +815,12 @@ export function createSessionBrowserMethods(options = {}) {
|
|
|
562
815
|
const range = normalizedRange === 'all' ? 'all' : (normalizedRange === '30d' ? '30d' : '7d');
|
|
563
816
|
const defaultLimit = range === 'all' ? 2000 : (range === '30d' ? 1200 : 600);
|
|
564
817
|
const rawLimit = Number(options.limit);
|
|
565
|
-
const
|
|
818
|
+
const compareBoost = this.sessionsUsageCompareEnabled && range !== 'all'
|
|
819
|
+
? 2000
|
|
820
|
+
: defaultLimit;
|
|
821
|
+
const limit = Number.isFinite(rawLimit)
|
|
822
|
+
? Math.max(1, Math.min(rawLimit, 2000))
|
|
823
|
+
: compareBoost;
|
|
566
824
|
const loadedLimit = Number(this.sessionsUsageLoadedLimit || 0);
|
|
567
825
|
if (this.sessionsUsageLoadedOnce && !options.forceRefresh && loadedLimit >= limit) {
|
|
568
826
|
return;
|
|
@@ -591,11 +849,15 @@ export function createSessionBrowserMethods(options = {}) {
|
|
|
591
849
|
if (loadSucceeded) {
|
|
592
850
|
this.sessionsUsageLoadedOnce = true;
|
|
593
851
|
this.sessionsUsageLoadedLimit = limit;
|
|
852
|
+
if (!this.sessionsUsageSelectedDayKey && Array.isArray(this.sessionUsageDailyTableRows) && this.sessionUsageDailyTableRows.length > 0) {
|
|
853
|
+
this.sessionsUsageSelectedDayKey = this.sessionUsageDailyTableRows[0].key;
|
|
854
|
+
}
|
|
594
855
|
}
|
|
595
856
|
}
|
|
596
857
|
},
|
|
597
858
|
|
|
598
859
|
async loadSessions(options = {}) {
|
|
860
|
+
this.resetSessionListMessageCountHydrate();
|
|
599
861
|
const result = await loadSessionsHelper.call(this, api, options || {});
|
|
600
862
|
this.pruneSessionPinnedMap(this.sessionsList);
|
|
601
863
|
return result;
|
|
@@ -686,7 +948,8 @@ export function createSessionBrowserMethods(options = {}) {
|
|
|
686
948
|
const res = await api('session-plain', {
|
|
687
949
|
source: sessionSnapshot.source,
|
|
688
950
|
sessionId: sessionSnapshot.sessionId,
|
|
689
|
-
filePath: sessionSnapshot.filePath
|
|
951
|
+
filePath: sessionSnapshot.filePath,
|
|
952
|
+
maxMessages: sessionSnapshot.maxMessages || 50
|
|
690
953
|
});
|
|
691
954
|
|
|
692
955
|
if (requestSeq !== this.sessionStandaloneRequestSeq) {
|
|
@@ -716,7 +979,11 @@ export function createSessionBrowserMethods(options = {}) {
|
|
|
716
979
|
},
|
|
717
980
|
|
|
718
981
|
async loadActiveSessionDetail(options = {}) {
|
|
719
|
-
|
|
982
|
+
const result = await loadActiveSessionDetailHelper.call(this, api, options);
|
|
983
|
+
if (this.mainTab === 'sessions' && typeof this.scheduleSessionListMessageCountHydrate === 'function') {
|
|
984
|
+
this.scheduleSessionListMessageCountHydrate();
|
|
985
|
+
}
|
|
986
|
+
return result;
|
|
720
987
|
}
|
|
721
988
|
};
|
|
722
989
|
}
|
|
@@ -256,14 +256,45 @@ export function createSessionTimelineMethods() {
|
|
|
256
256
|
},
|
|
257
257
|
onSessionPreviewScroll() {
|
|
258
258
|
if (
|
|
259
|
-
|
|
260
|
-
|| this.mainTab !== 'sessions'
|
|
259
|
+
this.mainTab !== 'sessions'
|
|
261
260
|
|| this.getMainTabForNav() !== 'sessions'
|
|
262
261
|
|| !this.sessionPreviewRenderEnabled
|
|
263
262
|
) return;
|
|
264
|
-
if (!this.sessionTimelineNodes.length) return;
|
|
265
263
|
const scrollEl = this.sessionPreviewScrollEl || this.$refs.sessionPreviewScroll;
|
|
266
264
|
if (!scrollEl) return;
|
|
265
|
+
|
|
266
|
+
if (
|
|
267
|
+
this.canLoadMoreSessionMessages
|
|
268
|
+
&& !this.sessionPreviewLoadingMore
|
|
269
|
+
&& !this.sessionDetailLoading
|
|
270
|
+
&& typeof this.loadMoreSessionMessages === 'function'
|
|
271
|
+
) {
|
|
272
|
+
const now = Date.now();
|
|
273
|
+
const lastAt = Number(this.sessionPreviewAutoLoadLastAt || 0);
|
|
274
|
+
if (!this.sessionPreviewAutoLoadPending && (now - lastAt) >= 200) {
|
|
275
|
+
const scrollTop = Number(scrollEl.scrollTop || 0);
|
|
276
|
+
const clientHeight = Number(scrollEl.clientHeight || 0);
|
|
277
|
+
const scrollHeight = Number(scrollEl.scrollHeight || 0);
|
|
278
|
+
const threshold = 240;
|
|
279
|
+
if (
|
|
280
|
+
Number.isFinite(scrollTop)
|
|
281
|
+
&& Number.isFinite(clientHeight)
|
|
282
|
+
&& Number.isFinite(scrollHeight)
|
|
283
|
+
&& (scrollTop + clientHeight) >= (scrollHeight - threshold)
|
|
284
|
+
) {
|
|
285
|
+
this.sessionPreviewAutoLoadLastAt = now;
|
|
286
|
+
this.sessionPreviewAutoLoadPending = true;
|
|
287
|
+
Promise.resolve(this.loadMoreSessionMessages()).finally(() => {
|
|
288
|
+
this.sessionPreviewAutoLoadPending = false;
|
|
289
|
+
});
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
if (!this.sessionTimelineEnabled) {
|
|
295
|
+
return;
|
|
296
|
+
}
|
|
297
|
+
if (!this.sessionTimelineNodes.length) return;
|
|
267
298
|
const now = Date.now();
|
|
268
299
|
const currentTop = Number(scrollEl.scrollTop || 0);
|
|
269
300
|
const delta = Math.abs(currentTop - Number(this.sessionTimelineLastScrollTop || 0));
|
|
@@ -270,6 +270,20 @@ export function createSessionTrashMethods(options = {}) {
|
|
|
270
270
|
}
|
|
271
271
|
},
|
|
272
272
|
|
|
273
|
+
normalizeSessionTrashRetentionDays(value) {
|
|
274
|
+
const numeric = Number(value);
|
|
275
|
+
if (!Number.isFinite(numeric) || numeric < 1) {
|
|
276
|
+
return 30;
|
|
277
|
+
}
|
|
278
|
+
return Math.min(365, Math.max(1, Math.floor(numeric)));
|
|
279
|
+
},
|
|
280
|
+
|
|
281
|
+
setSessionTrashRetentionDays(days) {
|
|
282
|
+
const normalized = this.normalizeSessionTrashRetentionDays(days);
|
|
283
|
+
this.sessionTrashRetentionDays = normalized;
|
|
284
|
+
try { localStorage.setItem('codexmateSessionTrashRetentionDays', String(normalized)); } catch (_) {}
|
|
285
|
+
},
|
|
286
|
+
|
|
273
287
|
getSessionTrashActionKey(item) {
|
|
274
288
|
return item && typeof item.trashId === 'string' ? item.trashId : '';
|
|
275
289
|
},
|
|
@@ -294,7 +308,8 @@ export function createSessionTrashMethods(options = {}) {
|
|
|
294
308
|
try {
|
|
295
309
|
const res = await api('list-session-trash', {
|
|
296
310
|
limit: sessionTrashListLimit,
|
|
297
|
-
forceRefresh: !!options.forceRefresh
|
|
311
|
+
forceRefresh: !!options.forceRefresh,
|
|
312
|
+
retentionDays: this.sessionTrashRetentionDays
|
|
298
313
|
});
|
|
299
314
|
if (!this.isLatestSessionTrashListRequestToken(requestToken)) {
|
|
300
315
|
return;
|