codexmate 0.0.12 → 0.0.14

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/web-ui/logic.mjs CHANGED
@@ -128,10 +128,31 @@ export function buildSpeedTestIssue(name, result) {
128
128
 
129
129
  // Session filtering helpers
130
130
  export function isSessionQueryEnabled(source) {
131
- const normalized = (source || '').toLowerCase();
131
+ const normalized = normalizeSessionSource(source, '');
132
132
  return normalized === 'codex' || normalized === 'claude' || normalized === 'all';
133
133
  }
134
134
 
135
+ export function normalizeSessionSource(source, fallback = 'all') {
136
+ const normalized = typeof source === 'string'
137
+ ? source.trim().toLowerCase()
138
+ : '';
139
+ if (normalized === 'codex' || normalized === 'claude' || normalized === 'all') {
140
+ return normalized;
141
+ }
142
+ return fallback;
143
+ }
144
+
145
+ export function normalizeSessionPathFilter(pathFilter) {
146
+ return typeof pathFilter === 'string' ? pathFilter.trim() : '';
147
+ }
148
+
149
+ export function buildSessionFilterCacheState(source, pathFilter) {
150
+ return {
151
+ source: normalizeSessionSource(source, 'all'),
152
+ pathFilter: normalizeSessionPathFilter(pathFilter)
153
+ };
154
+ }
155
+
135
156
  export function buildSessionListParams(options = {}) {
136
157
  const {
137
158
  source = 'all',
@@ -155,3 +176,128 @@ export function buildSessionListParams(options = {}) {
155
176
  forceRefresh: true
156
177
  };
157
178
  }
179
+
180
+ export function normalizeSessionMessageRole(role) {
181
+ const value = typeof role === 'string' ? role.trim().toLowerCase() : '';
182
+ if (value === 'user' || value === 'assistant' || value === 'system') {
183
+ return value;
184
+ }
185
+ return 'assistant';
186
+ }
187
+
188
+ function toRoleMeta(role) {
189
+ if (role === 'user') {
190
+ return { role: 'user', roleLabel: 'User', roleShort: 'U' };
191
+ }
192
+ if (role === 'assistant') {
193
+ return { role: 'assistant', roleLabel: 'Assistant', roleShort: 'A' };
194
+ }
195
+ if (role === 'system') {
196
+ return { role: 'system', roleLabel: 'System', roleShort: 'S' };
197
+ }
198
+ return { role: 'mixed', roleLabel: 'Mixed', roleShort: 'M' };
199
+ }
200
+
201
+ function clampTimelinePercent(percent) {
202
+ return Math.max(6, Math.min(94, percent));
203
+ }
204
+
205
+ export function formatSessionTimelineTimestamp(timestamp) {
206
+ const value = typeof timestamp === 'string' ? timestamp.trim() : '';
207
+ if (!value) return '';
208
+
209
+ // 优先按 ISO/常见时间串抽取,避免本地时区格式差异导致的展示抖动。
210
+ const matched = value.match(/^(\d{4})-(\d{2})-(\d{2})[T\s](\d{2}):(\d{2})(?::(\d{2}))?/);
211
+ if (matched) {
212
+ const second = matched[6] || '00';
213
+ return `${matched[2]}-${matched[3]} ${matched[4]}:${matched[5]}:${second}`;
214
+ }
215
+
216
+ return value;
217
+ }
218
+
219
+ export function buildSessionTimelineNodes(messages = [], options = {}) {
220
+ const list = Array.isArray(messages) ? messages : [];
221
+ const getKey = typeof options.getKey === 'function'
222
+ ? options.getKey
223
+ : ((_message, index) => `msg-${index}`);
224
+ const total = list.length;
225
+ const rawMaxMarkers = Number(options.maxMarkers);
226
+ const maxMarkers = Number.isFinite(rawMaxMarkers)
227
+ ? Math.max(1, Math.min(80, Math.floor(rawMaxMarkers)))
228
+ : 30;
229
+
230
+ const buildSingleNode = (message, index) => {
231
+ const role = normalizeSessionMessageRole(message && (message.normalizedRole || message.role));
232
+ const roleMeta = toRoleMeta(role);
233
+ const key = String(getKey(message, index) || `msg-${index}`);
234
+ const displayTime = formatSessionTimelineTimestamp(message && message.timestamp ? message.timestamp : '');
235
+ const title = displayTime
236
+ ? `#${index + 1} · ${roleMeta.roleLabel} · ${displayTime}`
237
+ : `#${index + 1} · ${roleMeta.roleLabel}`;
238
+ const percent = total <= 1 ? 0 : (index / (total - 1)) * 100;
239
+ return {
240
+ key,
241
+ role: roleMeta.role,
242
+ roleLabel: roleMeta.roleLabel,
243
+ roleShort: roleMeta.roleShort,
244
+ displayTime,
245
+ title,
246
+ percent,
247
+ safePercent: clampTimelinePercent(percent)
248
+ };
249
+ };
250
+
251
+ if (total <= maxMarkers) {
252
+ return list.map((message, index) => buildSingleNode(message, index));
253
+ }
254
+
255
+ const nodes = [];
256
+ const bucketWidth = total / maxMarkers;
257
+ for (let bucket = 0; bucket < maxMarkers; bucket += 1) {
258
+ let start = Math.floor(bucket * bucketWidth);
259
+ if (nodes.length && start <= nodes[nodes.length - 1].endIndex) {
260
+ start = nodes[nodes.length - 1].endIndex + 1;
261
+ }
262
+ if (start >= total) {
263
+ break;
264
+ }
265
+ let end = Math.floor((bucket + 1) * bucketWidth) - 1;
266
+ end = Math.max(start, Math.min(total - 1, end));
267
+ const targetIndex = Math.min(total - 1, start + Math.floor((end - start) / 2));
268
+ const targetMessage = list[targetIndex] || null;
269
+ const key = String(getKey(targetMessage, targetIndex) || `msg-${targetIndex}`);
270
+ const percent = total <= 1 ? 0 : (targetIndex / (total - 1)) * 100;
271
+ const messagesInGroup = end - start + 1;
272
+ const roleSet = new Set();
273
+ for (let i = start; i <= end; i += 1) {
274
+ roleSet.add(normalizeSessionMessageRole(list[i] && (list[i].normalizedRole || list[i].role)));
275
+ }
276
+ const roleValue = roleSet.size === 1 ? Array.from(roleSet)[0] : 'mixed';
277
+ const roleMeta = toRoleMeta(roleValue);
278
+ const firstTime = formatSessionTimelineTimestamp(list[start] && list[start].timestamp ? list[start].timestamp : '');
279
+ const lastTime = formatSessionTimelineTimestamp(list[end] && list[end].timestamp ? list[end].timestamp : '');
280
+ let displayTime = '';
281
+ if (firstTime && lastTime) {
282
+ displayTime = firstTime === lastTime ? firstTime : `${firstTime} ~ ${lastTime}`;
283
+ } else {
284
+ displayTime = firstTime || lastTime;
285
+ }
286
+ const titleBase = `#${start + 1}-${end + 1} · ${messagesInGroup} msgs · ${roleMeta.roleLabel}`;
287
+ const title = displayTime ? `${titleBase} · ${displayTime}` : titleBase;
288
+ nodes.push({
289
+ key,
290
+ role: roleMeta.role,
291
+ roleLabel: roleMeta.roleLabel,
292
+ roleShort: roleMeta.roleShort,
293
+ displayTime,
294
+ title,
295
+ percent,
296
+ safePercent: clampTimelinePercent(percent),
297
+ startIndex: start,
298
+ endIndex: end,
299
+ messageCount: messagesInGroup
300
+ });
301
+ }
302
+ return nodes;
303
+ }