claude-code-templates 1.5.1 → 1.5.3
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/package.json +1 -1
- package/src/analytics.js +307 -175
package/package.json
CHANGED
package/src/analytics.js
CHANGED
|
@@ -70,12 +70,33 @@ class ClaudeAnalytics {
|
|
|
70
70
|
const conversations = [];
|
|
71
71
|
|
|
72
72
|
try {
|
|
73
|
-
|
|
74
|
-
const
|
|
73
|
+
// Search for .jsonl files recursively in all subdirectories
|
|
74
|
+
const findJsonlFiles = async (dir) => {
|
|
75
|
+
const files = [];
|
|
76
|
+
const items = await fs.readdir(dir);
|
|
77
|
+
|
|
78
|
+
for (const item of items) {
|
|
79
|
+
const itemPath = path.join(dir, item);
|
|
80
|
+
const stats = await fs.stat(itemPath);
|
|
81
|
+
|
|
82
|
+
if (stats.isDirectory()) {
|
|
83
|
+
// Recursively search subdirectories
|
|
84
|
+
const subFiles = await findJsonlFiles(itemPath);
|
|
85
|
+
files.push(...subFiles);
|
|
86
|
+
} else if (item.endsWith('.jsonl')) {
|
|
87
|
+
files.push(itemPath);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return files;
|
|
92
|
+
};
|
|
75
93
|
|
|
76
|
-
|
|
77
|
-
|
|
94
|
+
const jsonlFiles = await findJsonlFiles(this.claudeDir);
|
|
95
|
+
console.log(chalk.blue(`Found ${jsonlFiles.length} conversation files`));
|
|
96
|
+
|
|
97
|
+
for (const filePath of jsonlFiles) {
|
|
78
98
|
const stats = await fs.stat(filePath);
|
|
99
|
+
const filename = path.basename(filePath);
|
|
79
100
|
|
|
80
101
|
try {
|
|
81
102
|
const content = await fs.readFile(filePath, 'utf8');
|
|
@@ -88,21 +109,25 @@ class ClaudeAnalytics {
|
|
|
88
109
|
}
|
|
89
110
|
}).filter(Boolean);
|
|
90
111
|
|
|
112
|
+
// Extract project name from path
|
|
113
|
+
const projectFromPath = this.extractProjectFromPath(filePath);
|
|
114
|
+
|
|
91
115
|
const conversation = {
|
|
92
|
-
id:
|
|
93
|
-
filename:
|
|
116
|
+
id: filename.replace('.jsonl', ''),
|
|
117
|
+
filename: filename,
|
|
118
|
+
filePath: filePath,
|
|
94
119
|
messageCount: messages.length,
|
|
95
120
|
fileSize: stats.size,
|
|
96
121
|
lastModified: stats.mtime,
|
|
97
122
|
created: stats.birthtime,
|
|
98
123
|
tokens: this.estimateTokens(content),
|
|
99
|
-
project: this.extractProjectFromConversation(messages),
|
|
124
|
+
project: projectFromPath || this.extractProjectFromConversation(messages),
|
|
100
125
|
status: this.determineConversationStatus(messages, stats.mtime)
|
|
101
126
|
};
|
|
102
127
|
|
|
103
128
|
conversations.push(conversation);
|
|
104
129
|
} catch (error) {
|
|
105
|
-
console.warn(chalk.yellow(`Warning: Could not parse ${
|
|
130
|
+
console.warn(chalk.yellow(`Warning: Could not parse ${filename}:`, error.message));
|
|
106
131
|
}
|
|
107
132
|
}
|
|
108
133
|
|
|
@@ -160,6 +185,27 @@ class ClaudeAnalytics {
|
|
|
160
185
|
return Math.ceil(text.length / 4);
|
|
161
186
|
}
|
|
162
187
|
|
|
188
|
+
extractProjectFromPath(filePath) {
|
|
189
|
+
// Extract project name from file path like:
|
|
190
|
+
// /Users/user/.claude/projects/-Users-user-Projects-MyProject/conversation.jsonl
|
|
191
|
+
const pathParts = filePath.split('/');
|
|
192
|
+
const projectIndex = pathParts.findIndex(part => part === 'projects');
|
|
193
|
+
|
|
194
|
+
if (projectIndex !== -1 && projectIndex + 1 < pathParts.length) {
|
|
195
|
+
const projectDir = pathParts[projectIndex + 1];
|
|
196
|
+
// Clean up the project directory name
|
|
197
|
+
const cleanName = projectDir
|
|
198
|
+
.replace(/^-/, '')
|
|
199
|
+
.replace(/-/g, '/')
|
|
200
|
+
.split('/')
|
|
201
|
+
.pop() || 'Unknown';
|
|
202
|
+
|
|
203
|
+
return cleanName;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
return null;
|
|
207
|
+
}
|
|
208
|
+
|
|
163
209
|
extractProjectFromConversation(messages) {
|
|
164
210
|
// Try to extract project information from conversation
|
|
165
211
|
for (const message of messages.slice(0, 5)) {
|
|
@@ -233,8 +279,10 @@ class ClaudeAnalytics {
|
|
|
233
279
|
setupFileWatchers() {
|
|
234
280
|
console.log(chalk.blue('👀 Setting up file watchers for real-time updates...'));
|
|
235
281
|
|
|
236
|
-
// Watch conversation files
|
|
237
|
-
const conversationWatcher = chokidar.watch(
|
|
282
|
+
// Watch conversation files recursively in all subdirectories
|
|
283
|
+
const conversationWatcher = chokidar.watch([
|
|
284
|
+
path.join(this.claudeDir, '**/*.jsonl')
|
|
285
|
+
], {
|
|
238
286
|
persistent: true,
|
|
239
287
|
ignoreInitial: true
|
|
240
288
|
});
|
|
@@ -257,7 +305,7 @@ class ClaudeAnalytics {
|
|
|
257
305
|
const projectWatcher = chokidar.watch(this.claudeDir, {
|
|
258
306
|
persistent: true,
|
|
259
307
|
ignoreInitial: true,
|
|
260
|
-
depth:
|
|
308
|
+
depth: 2 // Increased depth to catch subdirectories
|
|
261
309
|
});
|
|
262
310
|
|
|
263
311
|
projectWatcher.on('addDir', async () => {
|
|
@@ -397,7 +445,7 @@ async function createWebDashboard() {
|
|
|
397
445
|
<head>
|
|
398
446
|
<meta charset="UTF-8">
|
|
399
447
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
400
|
-
<title>Claude Code Analytics
|
|
448
|
+
<title>Claude Code Analytics - Terminal</title>
|
|
401
449
|
<style>
|
|
402
450
|
* {
|
|
403
451
|
margin: 0;
|
|
@@ -406,234 +454,292 @@ async function createWebDashboard() {
|
|
|
406
454
|
}
|
|
407
455
|
|
|
408
456
|
body {
|
|
409
|
-
font-family:
|
|
410
|
-
background:
|
|
411
|
-
color: #
|
|
457
|
+
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
|
|
458
|
+
background: #0d1117;
|
|
459
|
+
color: #c9d1d9;
|
|
412
460
|
min-height: 100vh;
|
|
461
|
+
line-height: 1.4;
|
|
413
462
|
}
|
|
414
463
|
|
|
415
|
-
.
|
|
416
|
-
max-width:
|
|
464
|
+
.terminal {
|
|
465
|
+
max-width: 1400px;
|
|
417
466
|
margin: 0 auto;
|
|
418
467
|
padding: 20px;
|
|
419
468
|
}
|
|
420
469
|
|
|
421
|
-
.header {
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
margin-bottom:
|
|
470
|
+
.terminal-header {
|
|
471
|
+
border-bottom: 1px solid #30363d;
|
|
472
|
+
padding-bottom: 20px;
|
|
473
|
+
margin-bottom: 20px;
|
|
425
474
|
}
|
|
426
475
|
|
|
427
|
-
.
|
|
428
|
-
|
|
429
|
-
|
|
476
|
+
.terminal-title {
|
|
477
|
+
color: #58a6ff;
|
|
478
|
+
font-size: 1.25rem;
|
|
479
|
+
font-weight: normal;
|
|
480
|
+
display: flex;
|
|
481
|
+
align-items: center;
|
|
482
|
+
gap: 8px;
|
|
430
483
|
}
|
|
431
484
|
|
|
432
|
-
.status-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
height: 12px;
|
|
485
|
+
.status-dot {
|
|
486
|
+
width: 8px;
|
|
487
|
+
height: 8px;
|
|
436
488
|
border-radius: 50%;
|
|
437
|
-
background: #
|
|
489
|
+
background: #3fb950;
|
|
438
490
|
animation: pulse 2s infinite;
|
|
439
|
-
margin-right: 8px;
|
|
440
491
|
}
|
|
441
492
|
|
|
442
493
|
@keyframes pulse {
|
|
443
494
|
0%, 100% { opacity: 1; }
|
|
444
|
-
50% { opacity: 0.
|
|
495
|
+
50% { opacity: 0.6; }
|
|
445
496
|
}
|
|
446
497
|
|
|
447
|
-
.
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
margin-bottom: 30px;
|
|
498
|
+
.terminal-subtitle {
|
|
499
|
+
color: #7d8590;
|
|
500
|
+
font-size: 0.875rem;
|
|
501
|
+
margin-top: 4px;
|
|
452
502
|
}
|
|
453
503
|
|
|
454
|
-
.
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
transition: transform 0.2s ease;
|
|
504
|
+
.stats-bar {
|
|
505
|
+
display: flex;
|
|
506
|
+
gap: 40px;
|
|
507
|
+
margin: 20px 0;
|
|
508
|
+
flex-wrap: wrap;
|
|
460
509
|
}
|
|
461
510
|
|
|
462
|
-
.stat
|
|
463
|
-
|
|
511
|
+
.stat {
|
|
512
|
+
display: flex;
|
|
513
|
+
align-items: center;
|
|
514
|
+
gap: 8px;
|
|
464
515
|
}
|
|
465
516
|
|
|
466
|
-
.stat-
|
|
467
|
-
color: #
|
|
517
|
+
.stat-label {
|
|
518
|
+
color: #7d8590;
|
|
468
519
|
font-size: 0.875rem;
|
|
469
|
-
text-transform: uppercase;
|
|
470
|
-
letter-spacing: 0.05em;
|
|
471
|
-
margin-bottom: 8px;
|
|
472
520
|
}
|
|
473
521
|
|
|
474
|
-
.stat-
|
|
475
|
-
|
|
522
|
+
.stat-value {
|
|
523
|
+
color: #58a6ff;
|
|
476
524
|
font-weight: bold;
|
|
477
|
-
color: #1f2937;
|
|
478
|
-
margin-bottom: 4px;
|
|
479
525
|
}
|
|
480
526
|
|
|
481
|
-
.
|
|
482
|
-
|
|
527
|
+
.filter-bar {
|
|
528
|
+
display: flex;
|
|
529
|
+
align-items: center;
|
|
530
|
+
gap: 16px;
|
|
531
|
+
margin: 20px 0;
|
|
532
|
+
padding: 12px 0;
|
|
533
|
+
border-top: 1px solid #21262d;
|
|
534
|
+
border-bottom: 1px solid #21262d;
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
.filter-label {
|
|
538
|
+
color: #7d8590;
|
|
483
539
|
font-size: 0.875rem;
|
|
484
540
|
}
|
|
485
541
|
|
|
486
|
-
.
|
|
487
|
-
display:
|
|
488
|
-
|
|
489
|
-
gap: 20px;
|
|
542
|
+
.filter-buttons {
|
|
543
|
+
display: flex;
|
|
544
|
+
gap: 8px;
|
|
490
545
|
}
|
|
491
546
|
|
|
492
|
-
.
|
|
493
|
-
background:
|
|
494
|
-
border
|
|
495
|
-
|
|
496
|
-
|
|
547
|
+
.filter-btn {
|
|
548
|
+
background: none;
|
|
549
|
+
border: 1px solid #30363d;
|
|
550
|
+
color: #7d8590;
|
|
551
|
+
padding: 4px 12px;
|
|
552
|
+
border-radius: 4px;
|
|
553
|
+
cursor: pointer;
|
|
554
|
+
font-family: inherit;
|
|
555
|
+
font-size: 0.875rem;
|
|
556
|
+
transition: all 0.2s ease;
|
|
497
557
|
}
|
|
498
558
|
|
|
499
|
-
.
|
|
500
|
-
color: #
|
|
501
|
-
|
|
502
|
-
font-size: 1.25rem;
|
|
559
|
+
.filter-btn:hover {
|
|
560
|
+
border-color: #58a6ff;
|
|
561
|
+
color: #58a6ff;
|
|
503
562
|
}
|
|
504
563
|
|
|
505
|
-
.
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
padding: 12px 0;
|
|
510
|
-
border-bottom: 1px solid #f3f4f6;
|
|
564
|
+
.filter-btn.active {
|
|
565
|
+
background: #58a6ff;
|
|
566
|
+
border-color: #58a6ff;
|
|
567
|
+
color: #0d1117;
|
|
511
568
|
}
|
|
512
569
|
|
|
513
|
-
.
|
|
514
|
-
|
|
570
|
+
.sessions-table {
|
|
571
|
+
width: 100%;
|
|
572
|
+
border-collapse: collapse;
|
|
515
573
|
}
|
|
516
574
|
|
|
517
|
-
.
|
|
518
|
-
|
|
575
|
+
.sessions-table th {
|
|
576
|
+
text-align: left;
|
|
577
|
+
padding: 8px 12px;
|
|
578
|
+
color: #7d8590;
|
|
519
579
|
font-size: 0.875rem;
|
|
520
|
-
|
|
580
|
+
font-weight: normal;
|
|
581
|
+
border-bottom: 1px solid #30363d;
|
|
521
582
|
}
|
|
522
583
|
|
|
523
|
-
.
|
|
524
|
-
|
|
525
|
-
font-size: 0.
|
|
584
|
+
.sessions-table td {
|
|
585
|
+
padding: 8px 12px;
|
|
586
|
+
font-size: 0.875rem;
|
|
587
|
+
border-bottom: 1px solid #21262d;
|
|
526
588
|
}
|
|
527
589
|
|
|
528
|
-
.
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
590
|
+
.sessions-table tr:hover {
|
|
591
|
+
background: #161b22;
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
.session-id {
|
|
595
|
+
color: #58a6ff;
|
|
596
|
+
font-family: monospace;
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
.session-project {
|
|
600
|
+
color: #c9d1d9;
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
.session-messages {
|
|
604
|
+
color: #7d8590;
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
.session-tokens {
|
|
608
|
+
color: #f85149;
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
.session-time {
|
|
612
|
+
color: #7d8590;
|
|
613
|
+
font-size: 0.8rem;
|
|
533
614
|
}
|
|
534
615
|
|
|
535
616
|
.status-active {
|
|
536
|
-
|
|
537
|
-
|
|
617
|
+
color: #3fb950;
|
|
618
|
+
font-weight: bold;
|
|
538
619
|
}
|
|
539
620
|
|
|
540
621
|
.status-recent {
|
|
541
|
-
|
|
542
|
-
color: #92400e;
|
|
622
|
+
color: #d29922;
|
|
543
623
|
}
|
|
544
624
|
|
|
545
625
|
.status-inactive {
|
|
546
|
-
|
|
547
|
-
color: #6b7280;
|
|
626
|
+
color: #7d8590;
|
|
548
627
|
}
|
|
549
628
|
|
|
550
|
-
.loading {
|
|
629
|
+
.loading, .error {
|
|
551
630
|
text-align: center;
|
|
552
|
-
color: white;
|
|
553
631
|
padding: 40px;
|
|
632
|
+
color: #7d8590;
|
|
554
633
|
}
|
|
555
634
|
|
|
556
635
|
.error {
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
636
|
+
color: #f85149;
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
.no-sessions {
|
|
640
|
+
text-align: center;
|
|
641
|
+
padding: 40px;
|
|
642
|
+
color: #7d8590;
|
|
643
|
+
font-style: italic;
|
|
562
644
|
}
|
|
563
645
|
|
|
564
646
|
@media (max-width: 768px) {
|
|
565
|
-
.
|
|
566
|
-
|
|
647
|
+
.stats-bar {
|
|
648
|
+
gap: 20px;
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
.filter-bar {
|
|
652
|
+
flex-direction: column;
|
|
653
|
+
align-items: flex-start;
|
|
654
|
+
gap: 8px;
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
.sessions-table {
|
|
658
|
+
font-size: 0.8rem;
|
|
567
659
|
}
|
|
568
660
|
|
|
569
|
-
.
|
|
570
|
-
|
|
661
|
+
.sessions-table th,
|
|
662
|
+
.sessions-table td {
|
|
663
|
+
padding: 6px 8px;
|
|
571
664
|
}
|
|
572
665
|
}
|
|
573
666
|
</style>
|
|
574
667
|
</head>
|
|
575
668
|
<body>
|
|
576
|
-
<div class="
|
|
577
|
-
<div class="header">
|
|
578
|
-
<
|
|
579
|
-
<span class="status-
|
|
580
|
-
|
|
581
|
-
</
|
|
582
|
-
<
|
|
583
|
-
<
|
|
669
|
+
<div class="terminal">
|
|
670
|
+
<div class="terminal-header">
|
|
671
|
+
<div class="terminal-title">
|
|
672
|
+
<span class="status-dot"></span>
|
|
673
|
+
claude-code-analytics
|
|
674
|
+
</div>
|
|
675
|
+
<div class="terminal-subtitle">real-time monitoring dashboard</div>
|
|
676
|
+
<div class="terminal-subtitle" id="lastUpdate"></div>
|
|
584
677
|
</div>
|
|
585
678
|
|
|
586
679
|
<div id="loading" class="loading">
|
|
587
|
-
|
|
680
|
+
loading claude code data...
|
|
588
681
|
</div>
|
|
589
682
|
|
|
590
683
|
<div id="error" class="error" style="display: none;">
|
|
591
|
-
|
|
684
|
+
error: failed to load claude code data
|
|
592
685
|
</div>
|
|
593
686
|
|
|
594
687
|
<div id="dashboard" style="display: none;">
|
|
595
|
-
<div class="stats-
|
|
596
|
-
<div class="stat
|
|
597
|
-
<
|
|
598
|
-
<
|
|
599
|
-
<div class="label">Conversations</div>
|
|
688
|
+
<div class="stats-bar">
|
|
689
|
+
<div class="stat">
|
|
690
|
+
<span class="stat-label">sessions:</span>
|
|
691
|
+
<span class="stat-value" id="totalSessions">0</span>
|
|
600
692
|
</div>
|
|
601
|
-
<div class="stat
|
|
602
|
-
<
|
|
603
|
-
<
|
|
604
|
-
<div class="label">Estimated</div>
|
|
693
|
+
<div class="stat">
|
|
694
|
+
<span class="stat-label">tokens:</span>
|
|
695
|
+
<span class="stat-value" id="totalTokens">0</span>
|
|
605
696
|
</div>
|
|
606
|
-
<div class="stat
|
|
607
|
-
<
|
|
608
|
-
<
|
|
609
|
-
<div class="label">Currently</div>
|
|
697
|
+
<div class="stat">
|
|
698
|
+
<span class="stat-label">projects:</span>
|
|
699
|
+
<span class="stat-value" id="activeProjects">0</span>
|
|
610
700
|
</div>
|
|
611
|
-
<div class="stat
|
|
612
|
-
<
|
|
613
|
-
<
|
|
614
|
-
<div class="label">Total</div>
|
|
701
|
+
<div class="stat">
|
|
702
|
+
<span class="stat-label">storage:</span>
|
|
703
|
+
<span class="stat-value" id="dataSize">0</span>
|
|
615
704
|
</div>
|
|
616
705
|
</div>
|
|
617
706
|
|
|
618
|
-
<div class="
|
|
619
|
-
<
|
|
620
|
-
|
|
621
|
-
<
|
|
622
|
-
|
|
623
|
-
</
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
<div class="panel">
|
|
627
|
-
<h2>Active Projects</h2>
|
|
628
|
-
<div id="projects">
|
|
629
|
-
<!-- Projects will be loaded here -->
|
|
630
|
-
</div>
|
|
707
|
+
<div class="filter-bar">
|
|
708
|
+
<span class="filter-label">filter sessions:</span>
|
|
709
|
+
<div class="filter-buttons">
|
|
710
|
+
<button class="filter-btn active" data-filter="active">active</button>
|
|
711
|
+
<button class="filter-btn" data-filter="recent">recent</button>
|
|
712
|
+
<button class="filter-btn" data-filter="inactive">inactive</button>
|
|
713
|
+
<button class="filter-btn" data-filter="all">all</button>
|
|
631
714
|
</div>
|
|
632
715
|
</div>
|
|
716
|
+
|
|
717
|
+
<table class="sessions-table">
|
|
718
|
+
<thead>
|
|
719
|
+
<tr>
|
|
720
|
+
<th>session id</th>
|
|
721
|
+
<th>project</th>
|
|
722
|
+
<th>messages</th>
|
|
723
|
+
<th>tokens</th>
|
|
724
|
+
<th>last activity</th>
|
|
725
|
+
<th>status</th>
|
|
726
|
+
</tr>
|
|
727
|
+
</thead>
|
|
728
|
+
<tbody id="sessionsTable">
|
|
729
|
+
<!-- Sessions will be loaded here -->
|
|
730
|
+
</tbody>
|
|
731
|
+
</table>
|
|
732
|
+
|
|
733
|
+
<div id="noSessions" class="no-sessions" style="display: none;">
|
|
734
|
+
no sessions found for current filter
|
|
735
|
+
</div>
|
|
633
736
|
</div>
|
|
634
737
|
</div>
|
|
635
738
|
|
|
636
739
|
<script>
|
|
740
|
+
let allConversations = [];
|
|
741
|
+
let currentFilter = 'active';
|
|
742
|
+
|
|
637
743
|
async function loadData() {
|
|
638
744
|
try {
|
|
639
745
|
const response = await fetch('/api/data');
|
|
@@ -646,11 +752,11 @@ async function createWebDashboard() {
|
|
|
646
752
|
|
|
647
753
|
// Update timestamp
|
|
648
754
|
document.getElementById('lastUpdate').textContent =
|
|
649
|
-
\`
|
|
755
|
+
\`last update: \${data.lastUpdate}\`;
|
|
650
756
|
|
|
651
757
|
updateStats(data.summary);
|
|
652
|
-
|
|
653
|
-
|
|
758
|
+
allConversations = data.conversations;
|
|
759
|
+
updateSessionsTable();
|
|
654
760
|
|
|
655
761
|
} catch (error) {
|
|
656
762
|
document.getElementById('loading').style.display = 'none';
|
|
@@ -666,44 +772,70 @@ async function createWebDashboard() {
|
|
|
666
772
|
document.getElementById('dataSize').textContent = summary.totalFileSize;
|
|
667
773
|
}
|
|
668
774
|
|
|
669
|
-
function
|
|
670
|
-
const
|
|
775
|
+
function updateSessionsTable() {
|
|
776
|
+
const tableBody = document.getElementById('sessionsTable');
|
|
777
|
+
const noSessionsDiv = document.getElementById('noSessions');
|
|
671
778
|
|
|
672
|
-
|
|
673
|
-
|
|
779
|
+
// Filter conversations based on current filter
|
|
780
|
+
let filteredConversations = allConversations;
|
|
781
|
+
if (currentFilter !== 'all') {
|
|
782
|
+
filteredConversations = allConversations.filter(conv => conv.status === currentFilter);
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
if (filteredConversations.length === 0) {
|
|
786
|
+
tableBody.innerHTML = '';
|
|
787
|
+
noSessionsDiv.style.display = 'block';
|
|
674
788
|
return;
|
|
675
789
|
}
|
|
676
790
|
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
</
|
|
683
|
-
<
|
|
684
|
-
|
|
791
|
+
noSessionsDiv.style.display = 'none';
|
|
792
|
+
|
|
793
|
+
tableBody.innerHTML = filteredConversations.map(conv => \`
|
|
794
|
+
<tr>
|
|
795
|
+
<td class="session-id">\${conv.id.substring(0, 8)}...</td>
|
|
796
|
+
<td class="session-project">\${conv.project}</td>
|
|
797
|
+
<td class="session-messages">\${conv.messageCount}</td>
|
|
798
|
+
<td class="session-tokens">\${conv.tokens.toLocaleString()}</td>
|
|
799
|
+
<td class="session-time">\${formatTime(conv.lastModified)}</td>
|
|
800
|
+
<td class="status-\${conv.status}">\${conv.status}</td>
|
|
801
|
+
</tr>
|
|
685
802
|
\`).join('');
|
|
686
803
|
}
|
|
687
804
|
|
|
688
|
-
function
|
|
689
|
-
const
|
|
805
|
+
function formatTime(date) {
|
|
806
|
+
const now = new Date();
|
|
807
|
+
const diff = now - new Date(date);
|
|
808
|
+
const minutes = Math.floor(diff / (1000 * 60));
|
|
809
|
+
const hours = Math.floor(minutes / 60);
|
|
810
|
+
const days = Math.floor(hours / 24);
|
|
690
811
|
|
|
691
|
-
if (
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
}
|
|
695
|
-
|
|
696
|
-
container.innerHTML = projects.slice(0, 10).map(project => \`
|
|
697
|
-
<div class="project-item">
|
|
698
|
-
<div class="item-info">
|
|
699
|
-
<h4>\${project.name}</h4>
|
|
700
|
-
<p>\${project.todoFiles} todo files</p>
|
|
701
|
-
</div>
|
|
702
|
-
<span class="status-badge status-\${project.status}">\${project.status}</span>
|
|
703
|
-
</div>
|
|
704
|
-
\`).join('');
|
|
812
|
+
if (minutes < 1) return 'now';
|
|
813
|
+
if (minutes < 60) return \`\${minutes}m ago\`;
|
|
814
|
+
if (hours < 24) return \`\${hours}h ago\`;
|
|
815
|
+
return \`\${days}d ago\`;
|
|
705
816
|
}
|
|
706
817
|
|
|
818
|
+
// Filter button handlers
|
|
819
|
+
document.addEventListener('DOMContentLoaded', function() {
|
|
820
|
+
const filterButtons = document.querySelectorAll('.filter-btn');
|
|
821
|
+
|
|
822
|
+
filterButtons.forEach(button => {
|
|
823
|
+
button.addEventListener('click', function() {
|
|
824
|
+
// Remove active class from all buttons
|
|
825
|
+
filterButtons.forEach(btn => btn.classList.remove('active'));
|
|
826
|
+
|
|
827
|
+
// Add active class to clicked button
|
|
828
|
+
this.classList.add('active');
|
|
829
|
+
|
|
830
|
+
// Update current filter
|
|
831
|
+
currentFilter = this.dataset.filter;
|
|
832
|
+
|
|
833
|
+
// Update table
|
|
834
|
+
updateSessionsTable();
|
|
835
|
+
});
|
|
836
|
+
});
|
|
837
|
+
});
|
|
838
|
+
|
|
707
839
|
// Manual refresh function
|
|
708
840
|
async function forceRefresh() {
|
|
709
841
|
try {
|