claude-code-templates 1.5.2 → 1.5.4
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 +586 -157
package/package.json
CHANGED
package/src/analytics.js
CHANGED
|
@@ -364,6 +364,39 @@ class ClaudeAnalytics {
|
|
|
364
364
|
});
|
|
365
365
|
});
|
|
366
366
|
|
|
367
|
+
// Session detail endpoint
|
|
368
|
+
this.app.get('/api/session/:sessionId', async (req, res) => {
|
|
369
|
+
const sessionId = req.params.sessionId;
|
|
370
|
+
|
|
371
|
+
try {
|
|
372
|
+
const session = this.data.conversations.find(conv => conv.id === sessionId);
|
|
373
|
+
if (!session) {
|
|
374
|
+
return res.status(404).json({ error: 'Session not found' });
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
// Read the actual conversation file
|
|
378
|
+
const content = await fs.readFile(session.filePath, 'utf8');
|
|
379
|
+
const lines = content.trim().split('\n').filter(line => line.trim());
|
|
380
|
+
const messages = lines.map(line => {
|
|
381
|
+
try {
|
|
382
|
+
return JSON.parse(line);
|
|
383
|
+
} catch {
|
|
384
|
+
return null;
|
|
385
|
+
}
|
|
386
|
+
}).filter(Boolean);
|
|
387
|
+
|
|
388
|
+
res.json({
|
|
389
|
+
session: session,
|
|
390
|
+
messages: messages,
|
|
391
|
+
timestamp: new Date().toISOString()
|
|
392
|
+
});
|
|
393
|
+
|
|
394
|
+
} catch (error) {
|
|
395
|
+
console.error(chalk.red('Error loading session details:'), error.message);
|
|
396
|
+
res.status(500).json({ error: 'Failed to load session details' });
|
|
397
|
+
}
|
|
398
|
+
});
|
|
399
|
+
|
|
367
400
|
// Main dashboard route
|
|
368
401
|
this.app.get('/', (req, res) => {
|
|
369
402
|
res.sendFile(path.join(__dirname, 'analytics-web', 'index.html'));
|
|
@@ -445,7 +478,7 @@ async function createWebDashboard() {
|
|
|
445
478
|
<head>
|
|
446
479
|
<meta charset="UTF-8">
|
|
447
480
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
448
|
-
<title>Claude Code Analytics
|
|
481
|
+
<title>Claude Code Analytics - Terminal</title>
|
|
449
482
|
<style>
|
|
450
483
|
* {
|
|
451
484
|
margin: 0;
|
|
@@ -454,227 +487,439 @@ async function createWebDashboard() {
|
|
|
454
487
|
}
|
|
455
488
|
|
|
456
489
|
body {
|
|
457
|
-
font-family:
|
|
458
|
-
background:
|
|
459
|
-
color: #
|
|
490
|
+
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
|
|
491
|
+
background: #0d1117;
|
|
492
|
+
color: #c9d1d9;
|
|
460
493
|
min-height: 100vh;
|
|
494
|
+
line-height: 1.4;
|
|
461
495
|
}
|
|
462
496
|
|
|
463
|
-
.
|
|
464
|
-
max-width:
|
|
497
|
+
.terminal {
|
|
498
|
+
max-width: 1400px;
|
|
465
499
|
margin: 0 auto;
|
|
466
500
|
padding: 20px;
|
|
467
501
|
}
|
|
468
502
|
|
|
469
|
-
.header {
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
margin-bottom:
|
|
503
|
+
.terminal-header {
|
|
504
|
+
border-bottom: 1px solid #30363d;
|
|
505
|
+
padding-bottom: 20px;
|
|
506
|
+
margin-bottom: 20px;
|
|
473
507
|
}
|
|
474
508
|
|
|
475
|
-
.
|
|
476
|
-
|
|
477
|
-
|
|
509
|
+
.terminal-title {
|
|
510
|
+
color: #58a6ff;
|
|
511
|
+
font-size: 1.25rem;
|
|
512
|
+
font-weight: normal;
|
|
513
|
+
display: flex;
|
|
514
|
+
align-items: center;
|
|
515
|
+
gap: 8px;
|
|
478
516
|
}
|
|
479
517
|
|
|
480
|
-
.status-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
height: 12px;
|
|
518
|
+
.status-dot {
|
|
519
|
+
width: 8px;
|
|
520
|
+
height: 8px;
|
|
484
521
|
border-radius: 50%;
|
|
485
|
-
background: #
|
|
522
|
+
background: #3fb950;
|
|
486
523
|
animation: pulse 2s infinite;
|
|
487
|
-
margin-right: 8px;
|
|
488
524
|
}
|
|
489
525
|
|
|
490
526
|
@keyframes pulse {
|
|
491
527
|
0%, 100% { opacity: 1; }
|
|
492
|
-
50% { opacity: 0.
|
|
528
|
+
50% { opacity: 0.6; }
|
|
493
529
|
}
|
|
494
530
|
|
|
495
|
-
.
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
margin-bottom: 30px;
|
|
531
|
+
.terminal-subtitle {
|
|
532
|
+
color: #7d8590;
|
|
533
|
+
font-size: 0.875rem;
|
|
534
|
+
margin-top: 4px;
|
|
500
535
|
}
|
|
501
536
|
|
|
502
|
-
.
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
transition: transform 0.2s ease;
|
|
537
|
+
.stats-bar {
|
|
538
|
+
display: flex;
|
|
539
|
+
gap: 40px;
|
|
540
|
+
margin: 20px 0;
|
|
541
|
+
flex-wrap: wrap;
|
|
508
542
|
}
|
|
509
543
|
|
|
510
|
-
.stat
|
|
511
|
-
|
|
544
|
+
.stat {
|
|
545
|
+
display: flex;
|
|
546
|
+
align-items: center;
|
|
547
|
+
gap: 8px;
|
|
512
548
|
}
|
|
513
549
|
|
|
514
|
-
.stat-
|
|
515
|
-
color: #
|
|
550
|
+
.stat-label {
|
|
551
|
+
color: #7d8590;
|
|
516
552
|
font-size: 0.875rem;
|
|
517
|
-
text-transform: uppercase;
|
|
518
|
-
letter-spacing: 0.05em;
|
|
519
|
-
margin-bottom: 8px;
|
|
520
553
|
}
|
|
521
554
|
|
|
522
|
-
.stat-
|
|
523
|
-
|
|
555
|
+
.stat-value {
|
|
556
|
+
color: #58a6ff;
|
|
524
557
|
font-weight: bold;
|
|
525
|
-
color: #1f2937;
|
|
526
|
-
margin-bottom: 4px;
|
|
527
558
|
}
|
|
528
559
|
|
|
529
|
-
.
|
|
530
|
-
|
|
560
|
+
.filter-bar {
|
|
561
|
+
display: flex;
|
|
562
|
+
align-items: center;
|
|
563
|
+
gap: 16px;
|
|
564
|
+
margin: 20px 0;
|
|
565
|
+
padding: 12px 0;
|
|
566
|
+
border-top: 1px solid #21262d;
|
|
567
|
+
border-bottom: 1px solid #21262d;
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
.filter-label {
|
|
571
|
+
color: #7d8590;
|
|
531
572
|
font-size: 0.875rem;
|
|
532
573
|
}
|
|
533
574
|
|
|
534
|
-
.
|
|
535
|
-
display:
|
|
536
|
-
|
|
537
|
-
gap: 20px;
|
|
575
|
+
.filter-buttons {
|
|
576
|
+
display: flex;
|
|
577
|
+
gap: 8px;
|
|
538
578
|
}
|
|
539
579
|
|
|
540
|
-
.
|
|
541
|
-
background:
|
|
542
|
-
border
|
|
543
|
-
|
|
544
|
-
|
|
580
|
+
.filter-btn {
|
|
581
|
+
background: none;
|
|
582
|
+
border: 1px solid #30363d;
|
|
583
|
+
color: #7d8590;
|
|
584
|
+
padding: 4px 12px;
|
|
585
|
+
border-radius: 4px;
|
|
586
|
+
cursor: pointer;
|
|
587
|
+
font-family: inherit;
|
|
588
|
+
font-size: 0.875rem;
|
|
589
|
+
transition: all 0.2s ease;
|
|
545
590
|
}
|
|
546
591
|
|
|
547
|
-
.
|
|
548
|
-
color: #
|
|
549
|
-
|
|
550
|
-
font-size: 1.25rem;
|
|
592
|
+
.filter-btn:hover {
|
|
593
|
+
border-color: #58a6ff;
|
|
594
|
+
color: #58a6ff;
|
|
551
595
|
}
|
|
552
596
|
|
|
553
|
-
.
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
padding: 12px 0;
|
|
558
|
-
border-bottom: 1px solid #f3f4f6;
|
|
597
|
+
.filter-btn.active {
|
|
598
|
+
background: #58a6ff;
|
|
599
|
+
border-color: #58a6ff;
|
|
600
|
+
color: #0d1117;
|
|
559
601
|
}
|
|
560
602
|
|
|
561
|
-
.
|
|
562
|
-
|
|
603
|
+
.sessions-table {
|
|
604
|
+
width: 100%;
|
|
605
|
+
border-collapse: collapse;
|
|
563
606
|
}
|
|
564
607
|
|
|
565
|
-
.
|
|
566
|
-
|
|
608
|
+
.sessions-table th {
|
|
609
|
+
text-align: left;
|
|
610
|
+
padding: 8px 12px;
|
|
611
|
+
color: #7d8590;
|
|
567
612
|
font-size: 0.875rem;
|
|
568
|
-
|
|
613
|
+
font-weight: normal;
|
|
614
|
+
border-bottom: 1px solid #30363d;
|
|
569
615
|
}
|
|
570
616
|
|
|
571
|
-
.
|
|
572
|
-
|
|
573
|
-
font-size: 0.
|
|
617
|
+
.sessions-table td {
|
|
618
|
+
padding: 8px 12px;
|
|
619
|
+
font-size: 0.875rem;
|
|
620
|
+
border-bottom: 1px solid #21262d;
|
|
574
621
|
}
|
|
575
622
|
|
|
576
|
-
.
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
623
|
+
.sessions-table tr:hover {
|
|
624
|
+
background: #161b22;
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
.session-id {
|
|
628
|
+
color: #58a6ff;
|
|
629
|
+
font-family: monospace;
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
.session-project {
|
|
633
|
+
color: #c9d1d9;
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
.session-messages {
|
|
637
|
+
color: #7d8590;
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
.session-tokens {
|
|
641
|
+
color: #f85149;
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
.session-time {
|
|
645
|
+
color: #7d8590;
|
|
646
|
+
font-size: 0.8rem;
|
|
581
647
|
}
|
|
582
648
|
|
|
583
649
|
.status-active {
|
|
584
|
-
|
|
585
|
-
|
|
650
|
+
color: #3fb950;
|
|
651
|
+
font-weight: bold;
|
|
586
652
|
}
|
|
587
653
|
|
|
588
654
|
.status-recent {
|
|
589
|
-
|
|
590
|
-
color: #92400e;
|
|
655
|
+
color: #d29922;
|
|
591
656
|
}
|
|
592
657
|
|
|
593
658
|
.status-inactive {
|
|
594
|
-
|
|
595
|
-
color: #6b7280;
|
|
659
|
+
color: #7d8590;
|
|
596
660
|
}
|
|
597
661
|
|
|
598
|
-
.loading {
|
|
662
|
+
.loading, .error {
|
|
599
663
|
text-align: center;
|
|
600
|
-
color: white;
|
|
601
664
|
padding: 40px;
|
|
665
|
+
color: #7d8590;
|
|
602
666
|
}
|
|
603
667
|
|
|
604
668
|
.error {
|
|
605
|
-
|
|
606
|
-
|
|
669
|
+
color: #f85149;
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
.no-sessions {
|
|
673
|
+
text-align: center;
|
|
674
|
+
padding: 40px;
|
|
675
|
+
color: #7d8590;
|
|
676
|
+
font-style: italic;
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
.session-detail {
|
|
680
|
+
display: none;
|
|
681
|
+
margin-top: 20px;
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
.session-detail.active {
|
|
685
|
+
display: block;
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
.detail-header {
|
|
689
|
+
display: flex;
|
|
690
|
+
justify-content: space-between;
|
|
691
|
+
align-items: center;
|
|
692
|
+
padding: 16px 0;
|
|
693
|
+
border-bottom: 1px solid #30363d;
|
|
694
|
+
margin-bottom: 20px;
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
.detail-title {
|
|
698
|
+
color: #58a6ff;
|
|
699
|
+
font-size: 1.1rem;
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
.detail-actions {
|
|
703
|
+
display: flex;
|
|
704
|
+
gap: 12px;
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
.btn {
|
|
708
|
+
background: none;
|
|
709
|
+
border: 1px solid #30363d;
|
|
710
|
+
color: #7d8590;
|
|
711
|
+
padding: 6px 12px;
|
|
712
|
+
border-radius: 4px;
|
|
713
|
+
cursor: pointer;
|
|
714
|
+
font-family: inherit;
|
|
715
|
+
font-size: 0.875rem;
|
|
716
|
+
transition: all 0.2s ease;
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
.btn:hover {
|
|
720
|
+
border-color: #58a6ff;
|
|
721
|
+
color: #58a6ff;
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
.btn-primary {
|
|
725
|
+
background: #58a6ff;
|
|
726
|
+
border-color: #58a6ff;
|
|
727
|
+
color: #0d1117;
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
.btn-primary:hover {
|
|
731
|
+
background: #79c0ff;
|
|
732
|
+
border-color: #79c0ff;
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
.session-info {
|
|
736
|
+
display: grid;
|
|
737
|
+
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
|
738
|
+
gap: 20px;
|
|
739
|
+
margin-bottom: 30px;
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
.info-item {
|
|
743
|
+
display: flex;
|
|
744
|
+
flex-direction: column;
|
|
745
|
+
gap: 4px;
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
.info-label {
|
|
749
|
+
color: #7d8590;
|
|
750
|
+
font-size: 0.75rem;
|
|
751
|
+
text-transform: uppercase;
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
.info-value {
|
|
755
|
+
color: #c9d1d9;
|
|
756
|
+
font-size: 0.875rem;
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
.conversation-history {
|
|
760
|
+
border: 1px solid #30363d;
|
|
761
|
+
border-radius: 6px;
|
|
762
|
+
max-height: 600px;
|
|
763
|
+
overflow-y: auto;
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
.message {
|
|
607
767
|
padding: 16px;
|
|
608
|
-
border-
|
|
609
|
-
|
|
768
|
+
border-bottom: 1px solid #21262d;
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
.message:last-child {
|
|
772
|
+
border-bottom: none;
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
.message-header {
|
|
776
|
+
display: flex;
|
|
777
|
+
justify-content: space-between;
|
|
778
|
+
align-items: center;
|
|
779
|
+
margin-bottom: 8px;
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
.message-role {
|
|
783
|
+
color: #58a6ff;
|
|
784
|
+
font-size: 0.875rem;
|
|
785
|
+
font-weight: bold;
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
.message-role.user {
|
|
789
|
+
color: #3fb950;
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
.message-role.assistant {
|
|
793
|
+
color: #58a6ff;
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
.message-time {
|
|
797
|
+
color: #7d8590;
|
|
798
|
+
font-size: 0.75rem;
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
.message-content {
|
|
802
|
+
color: #c9d1d9;
|
|
803
|
+
font-size: 0.875rem;
|
|
804
|
+
line-height: 1.5;
|
|
805
|
+
white-space: pre-wrap;
|
|
806
|
+
word-wrap: break-word;
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
.back-btn {
|
|
810
|
+
margin-bottom: 20px;
|
|
610
811
|
}
|
|
611
812
|
|
|
612
813
|
@media (max-width: 768px) {
|
|
613
|
-
.
|
|
614
|
-
|
|
814
|
+
.stats-bar {
|
|
815
|
+
gap: 20px;
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
.filter-bar {
|
|
819
|
+
flex-direction: column;
|
|
820
|
+
align-items: flex-start;
|
|
821
|
+
gap: 8px;
|
|
615
822
|
}
|
|
616
823
|
|
|
617
|
-
.
|
|
618
|
-
font-size:
|
|
824
|
+
.sessions-table {
|
|
825
|
+
font-size: 0.8rem;
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
.sessions-table th,
|
|
829
|
+
.sessions-table td {
|
|
830
|
+
padding: 6px 8px;
|
|
619
831
|
}
|
|
620
832
|
}
|
|
621
833
|
</style>
|
|
622
834
|
</head>
|
|
623
835
|
<body>
|
|
624
|
-
<div class="
|
|
625
|
-
<div class="header">
|
|
626
|
-
<
|
|
627
|
-
<span class="status-
|
|
628
|
-
|
|
629
|
-
</
|
|
630
|
-
<
|
|
631
|
-
<
|
|
836
|
+
<div class="terminal">
|
|
837
|
+
<div class="terminal-header">
|
|
838
|
+
<div class="terminal-title">
|
|
839
|
+
<span class="status-dot"></span>
|
|
840
|
+
claude-code-analytics
|
|
841
|
+
</div>
|
|
842
|
+
<div class="terminal-subtitle">real-time monitoring dashboard</div>
|
|
843
|
+
<div class="terminal-subtitle" id="lastUpdate"></div>
|
|
632
844
|
</div>
|
|
633
845
|
|
|
634
846
|
<div id="loading" class="loading">
|
|
635
|
-
|
|
847
|
+
loading claude code data...
|
|
636
848
|
</div>
|
|
637
849
|
|
|
638
850
|
<div id="error" class="error" style="display: none;">
|
|
639
|
-
|
|
851
|
+
error: failed to load claude code data
|
|
640
852
|
</div>
|
|
641
853
|
|
|
642
854
|
<div id="dashboard" style="display: none;">
|
|
643
|
-
<div class="stats-
|
|
644
|
-
<div class="stat
|
|
645
|
-
<
|
|
646
|
-
<
|
|
647
|
-
<div class="label">Conversations</div>
|
|
855
|
+
<div class="stats-bar">
|
|
856
|
+
<div class="stat">
|
|
857
|
+
<span class="stat-label">sessions:</span>
|
|
858
|
+
<span class="stat-value" id="totalSessions">0</span>
|
|
648
859
|
</div>
|
|
649
|
-
<div class="stat
|
|
650
|
-
<
|
|
651
|
-
<
|
|
652
|
-
<div class="label">Estimated</div>
|
|
860
|
+
<div class="stat">
|
|
861
|
+
<span class="stat-label">tokens:</span>
|
|
862
|
+
<span class="stat-value" id="totalTokens">0</span>
|
|
653
863
|
</div>
|
|
654
|
-
<div class="stat
|
|
655
|
-
<
|
|
656
|
-
<
|
|
657
|
-
<div class="label">Currently</div>
|
|
864
|
+
<div class="stat">
|
|
865
|
+
<span class="stat-label">projects:</span>
|
|
866
|
+
<span class="stat-value" id="activeProjects">0</span>
|
|
658
867
|
</div>
|
|
659
|
-
<div class="stat
|
|
660
|
-
<
|
|
661
|
-
<
|
|
662
|
-
<div class="label">Total</div>
|
|
868
|
+
<div class="stat">
|
|
869
|
+
<span class="stat-label">storage:</span>
|
|
870
|
+
<span class="stat-value" id="dataSize">0</span>
|
|
663
871
|
</div>
|
|
664
872
|
</div>
|
|
665
873
|
|
|
666
|
-
<div class="
|
|
667
|
-
<
|
|
668
|
-
|
|
669
|
-
<
|
|
670
|
-
|
|
874
|
+
<div class="filter-bar">
|
|
875
|
+
<span class="filter-label">filter sessions:</span>
|
|
876
|
+
<div class="filter-buttons">
|
|
877
|
+
<button class="filter-btn active" data-filter="active">active</button>
|
|
878
|
+
<button class="filter-btn" data-filter="recent">recent</button>
|
|
879
|
+
<button class="filter-btn" data-filter="inactive">inactive</button>
|
|
880
|
+
<button class="filter-btn" data-filter="all">all</button>
|
|
881
|
+
</div>
|
|
882
|
+
</div>
|
|
883
|
+
|
|
884
|
+
<table class="sessions-table">
|
|
885
|
+
<thead>
|
|
886
|
+
<tr>
|
|
887
|
+
<th>session id</th>
|
|
888
|
+
<th>project</th>
|
|
889
|
+
<th>messages</th>
|
|
890
|
+
<th>tokens</th>
|
|
891
|
+
<th>last activity</th>
|
|
892
|
+
<th>status</th>
|
|
893
|
+
</tr>
|
|
894
|
+
</thead>
|
|
895
|
+
<tbody id="sessionsTable">
|
|
896
|
+
<!-- Sessions will be loaded here -->
|
|
897
|
+
</tbody>
|
|
898
|
+
</table>
|
|
899
|
+
|
|
900
|
+
<div id="noSessions" class="no-sessions" style="display: none;">
|
|
901
|
+
no sessions found for current filter
|
|
902
|
+
</div>
|
|
903
|
+
|
|
904
|
+
<div id="sessionDetail" class="session-detail">
|
|
905
|
+
<button class="btn back-btn" onclick="showSessionsList()">← back to sessions</button>
|
|
906
|
+
|
|
907
|
+
<div class="detail-header">
|
|
908
|
+
<div class="detail-title" id="detailTitle">session details</div>
|
|
909
|
+
<div class="detail-actions">
|
|
910
|
+
<button class="btn" onclick="exportSessionCSV()">export csv</button>
|
|
911
|
+
<button class="btn btn-primary" onclick="refreshSessionDetail()">refresh</button>
|
|
671
912
|
</div>
|
|
672
913
|
</div>
|
|
673
914
|
|
|
674
|
-
<div class="
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
915
|
+
<div class="session-info" id="sessionInfo">
|
|
916
|
+
<!-- Session info will be loaded here -->
|
|
917
|
+
</div>
|
|
918
|
+
|
|
919
|
+
<div>
|
|
920
|
+
<h3 style="color: #7d8590; margin-bottom: 16px; font-size: 0.875rem; text-transform: uppercase;">conversation history</h3>
|
|
921
|
+
<div class="conversation-history" id="conversationHistory">
|
|
922
|
+
<!-- Conversation history will be loaded here -->
|
|
678
923
|
</div>
|
|
679
924
|
</div>
|
|
680
925
|
</div>
|
|
@@ -682,6 +927,10 @@ async function createWebDashboard() {
|
|
|
682
927
|
</div>
|
|
683
928
|
|
|
684
929
|
<script>
|
|
930
|
+
let allConversations = [];
|
|
931
|
+
let currentFilter = 'active';
|
|
932
|
+
let currentSession = null;
|
|
933
|
+
|
|
685
934
|
async function loadData() {
|
|
686
935
|
try {
|
|
687
936
|
const response = await fetch('/api/data');
|
|
@@ -694,11 +943,11 @@ async function createWebDashboard() {
|
|
|
694
943
|
|
|
695
944
|
// Update timestamp
|
|
696
945
|
document.getElementById('lastUpdate').textContent =
|
|
697
|
-
\`
|
|
946
|
+
\`last update: \${data.lastUpdate}\`;
|
|
698
947
|
|
|
699
948
|
updateStats(data.summary);
|
|
700
|
-
|
|
701
|
-
|
|
949
|
+
allConversations = data.conversations;
|
|
950
|
+
updateSessionsTable();
|
|
702
951
|
|
|
703
952
|
} catch (error) {
|
|
704
953
|
document.getElementById('loading').style.display = 'none';
|
|
@@ -714,42 +963,222 @@ async function createWebDashboard() {
|
|
|
714
963
|
document.getElementById('dataSize').textContent = summary.totalFileSize;
|
|
715
964
|
}
|
|
716
965
|
|
|
717
|
-
function
|
|
718
|
-
const
|
|
966
|
+
function updateSessionsTable() {
|
|
967
|
+
const tableBody = document.getElementById('sessionsTable');
|
|
968
|
+
const noSessionsDiv = document.getElementById('noSessions');
|
|
969
|
+
|
|
970
|
+
// Filter conversations based on current filter
|
|
971
|
+
let filteredConversations = allConversations;
|
|
972
|
+
if (currentFilter !== 'all') {
|
|
973
|
+
filteredConversations = allConversations.filter(conv => conv.status === currentFilter);
|
|
974
|
+
}
|
|
719
975
|
|
|
720
|
-
if (
|
|
721
|
-
|
|
976
|
+
if (filteredConversations.length === 0) {
|
|
977
|
+
tableBody.innerHTML = '';
|
|
978
|
+
noSessionsDiv.style.display = 'block';
|
|
722
979
|
return;
|
|
723
980
|
}
|
|
724
981
|
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
</
|
|
731
|
-
<
|
|
732
|
-
|
|
982
|
+
noSessionsDiv.style.display = 'none';
|
|
983
|
+
|
|
984
|
+
tableBody.innerHTML = filteredConversations.map(conv => \`
|
|
985
|
+
<tr onclick="showSessionDetail('\${conv.id}')" style="cursor: pointer;">
|
|
986
|
+
<td class="session-id">\${conv.id.substring(0, 8)}...</td>
|
|
987
|
+
<td class="session-project">\${conv.project}</td>
|
|
988
|
+
<td class="session-messages">\${conv.messageCount}</td>
|
|
989
|
+
<td class="session-tokens">\${conv.tokens.toLocaleString()}</td>
|
|
990
|
+
<td class="session-time">\${formatTime(conv.lastModified)}</td>
|
|
991
|
+
<td class="status-\${conv.status}">\${conv.status}</td>
|
|
992
|
+
</tr>
|
|
733
993
|
\`).join('');
|
|
734
994
|
}
|
|
735
995
|
|
|
736
|
-
function
|
|
737
|
-
const
|
|
996
|
+
function formatTime(date) {
|
|
997
|
+
const now = new Date();
|
|
998
|
+
const diff = now - new Date(date);
|
|
999
|
+
const minutes = Math.floor(diff / (1000 * 60));
|
|
1000
|
+
const hours = Math.floor(minutes / 60);
|
|
1001
|
+
const days = Math.floor(hours / 24);
|
|
738
1002
|
|
|
739
|
-
if (
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
}
|
|
1003
|
+
if (minutes < 1) return 'now';
|
|
1004
|
+
if (minutes < 60) return \`\${minutes}m ago\`;
|
|
1005
|
+
if (hours < 24) return \`\${hours}h ago\`;
|
|
1006
|
+
return \`\${days}d ago\`;
|
|
1007
|
+
}
|
|
1008
|
+
|
|
1009
|
+
// Filter button handlers
|
|
1010
|
+
document.addEventListener('DOMContentLoaded', function() {
|
|
1011
|
+
const filterButtons = document.querySelectorAll('.filter-btn');
|
|
743
1012
|
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
1013
|
+
filterButtons.forEach(button => {
|
|
1014
|
+
button.addEventListener('click', function() {
|
|
1015
|
+
// Remove active class from all buttons
|
|
1016
|
+
filterButtons.forEach(btn => btn.classList.remove('active'));
|
|
1017
|
+
|
|
1018
|
+
// Add active class to clicked button
|
|
1019
|
+
this.classList.add('active');
|
|
1020
|
+
|
|
1021
|
+
// Update current filter
|
|
1022
|
+
currentFilter = this.dataset.filter;
|
|
1023
|
+
|
|
1024
|
+
// Update table
|
|
1025
|
+
updateSessionsTable();
|
|
1026
|
+
});
|
|
1027
|
+
});
|
|
1028
|
+
});
|
|
1029
|
+
|
|
1030
|
+
// Session detail functions
|
|
1031
|
+
async function showSessionDetail(sessionId) {
|
|
1032
|
+
currentSession = allConversations.find(conv => conv.id === sessionId);
|
|
1033
|
+
if (!currentSession) return;
|
|
1034
|
+
|
|
1035
|
+
// Hide sessions list and show detail
|
|
1036
|
+
document.querySelector('.filter-bar').style.display = 'none';
|
|
1037
|
+
document.querySelector('.sessions-table').style.display = 'none';
|
|
1038
|
+
document.getElementById('noSessions').style.display = 'none';
|
|
1039
|
+
document.getElementById('sessionDetail').classList.add('active');
|
|
1040
|
+
|
|
1041
|
+
// Update title
|
|
1042
|
+
document.getElementById('detailTitle').textContent = \`session: \${sessionId.substring(0, 8)}...\`;
|
|
1043
|
+
|
|
1044
|
+
// Load session info
|
|
1045
|
+
updateSessionInfo(currentSession);
|
|
1046
|
+
|
|
1047
|
+
// Load conversation history
|
|
1048
|
+
await loadConversationHistory(currentSession);
|
|
1049
|
+
}
|
|
1050
|
+
|
|
1051
|
+
function showSessionsList() {
|
|
1052
|
+
document.getElementById('sessionDetail').classList.remove('active');
|
|
1053
|
+
document.querySelector('.filter-bar').style.display = 'flex';
|
|
1054
|
+
document.querySelector('.sessions-table').style.display = 'table';
|
|
1055
|
+
updateSessionsTable();
|
|
1056
|
+
currentSession = null;
|
|
1057
|
+
}
|
|
1058
|
+
|
|
1059
|
+
function updateSessionInfo(session) {
|
|
1060
|
+
const container = document.getElementById('sessionInfo');
|
|
1061
|
+
|
|
1062
|
+
container.innerHTML = \`
|
|
1063
|
+
<div class="info-item">
|
|
1064
|
+
<div class="info-label">session id</div>
|
|
1065
|
+
<div class="info-value">\${session.id}</div>
|
|
751
1066
|
</div>
|
|
752
|
-
|
|
1067
|
+
<div class="info-item">
|
|
1068
|
+
<div class="info-label">project</div>
|
|
1069
|
+
<div class="info-value">\${session.project}</div>
|
|
1070
|
+
</div>
|
|
1071
|
+
<div class="info-item">
|
|
1072
|
+
<div class="info-label">messages</div>
|
|
1073
|
+
<div class="info-value">\${session.messageCount}</div>
|
|
1074
|
+
</div>
|
|
1075
|
+
<div class="info-item">
|
|
1076
|
+
<div class="info-label">tokens (estimated)</div>
|
|
1077
|
+
<div class="info-value">\${session.tokens.toLocaleString()}</div>
|
|
1078
|
+
</div>
|
|
1079
|
+
<div class="info-item">
|
|
1080
|
+
<div class="info-label">file size</div>
|
|
1081
|
+
<div class="info-value">\${formatBytes(session.fileSize)}</div>
|
|
1082
|
+
</div>
|
|
1083
|
+
<div class="info-item">
|
|
1084
|
+
<div class="info-label">created</div>
|
|
1085
|
+
<div class="info-value">\${new Date(session.created).toLocaleString()}</div>
|
|
1086
|
+
</div>
|
|
1087
|
+
<div class="info-item">
|
|
1088
|
+
<div class="info-label">last modified</div>
|
|
1089
|
+
<div class="info-value">\${new Date(session.lastModified).toLocaleString()}</div>
|
|
1090
|
+
</div>
|
|
1091
|
+
<div class="info-item">
|
|
1092
|
+
<div class="info-label">status</div>
|
|
1093
|
+
<div class="info-value status-\${session.status}">\${session.status}</div>
|
|
1094
|
+
</div>
|
|
1095
|
+
\`;
|
|
1096
|
+
}
|
|
1097
|
+
|
|
1098
|
+
async function loadConversationHistory(session) {
|
|
1099
|
+
try {
|
|
1100
|
+
const response = await fetch(\`/api/session/\${session.id}\`);
|
|
1101
|
+
const sessionData = await response.json();
|
|
1102
|
+
|
|
1103
|
+
const container = document.getElementById('conversationHistory');
|
|
1104
|
+
|
|
1105
|
+
if (!sessionData.messages || sessionData.messages.length === 0) {
|
|
1106
|
+
container.innerHTML = '<div style="padding: 20px; text-align: center; color: #7d8590;">no messages found</div>';
|
|
1107
|
+
return;
|
|
1108
|
+
}
|
|
1109
|
+
|
|
1110
|
+
container.innerHTML = sessionData.messages.map((message, index) => \`
|
|
1111
|
+
<div class="message">
|
|
1112
|
+
<div class="message-header">
|
|
1113
|
+
<div class="message-role \${message.role}">\${message.role}</div>
|
|
1114
|
+
<div class="message-time">message #\${index + 1}</div>
|
|
1115
|
+
</div>
|
|
1116
|
+
<div class="message-content">\${truncateContent(message.content || 'no content')}</div>
|
|
1117
|
+
</div>
|
|
1118
|
+
\`).join('');
|
|
1119
|
+
|
|
1120
|
+
} catch (error) {
|
|
1121
|
+
document.getElementById('conversationHistory').innerHTML =
|
|
1122
|
+
'<div style="padding: 20px; text-align: center; color: #f85149;">error loading conversation history</div>';
|
|
1123
|
+
console.error('Failed to load conversation history:', error);
|
|
1124
|
+
}
|
|
1125
|
+
}
|
|
1126
|
+
|
|
1127
|
+
function truncateContent(content, maxLength = 500) {
|
|
1128
|
+
if (typeof content !== 'string') return 'no content';
|
|
1129
|
+
if (content.length <= maxLength) return content;
|
|
1130
|
+
return content.substring(0, maxLength) + '...';
|
|
1131
|
+
}
|
|
1132
|
+
|
|
1133
|
+
function formatBytes(bytes) {
|
|
1134
|
+
if (bytes === 0) return '0 B';
|
|
1135
|
+
const k = 1024;
|
|
1136
|
+
const sizes = ['B', 'KB', 'MB', 'GB'];
|
|
1137
|
+
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
1138
|
+
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
|
|
1139
|
+
}
|
|
1140
|
+
|
|
1141
|
+
function exportSessionCSV() {
|
|
1142
|
+
if (!currentSession) return;
|
|
1143
|
+
|
|
1144
|
+
// Create CSV content
|
|
1145
|
+
let csvContent = 'Session ID,Project,Message Count,Tokens,File Size,Created,Last Modified,Status\\n';
|
|
1146
|
+
csvContent += \`"\${currentSession.id}","\${currentSession.project}",\${currentSession.messageCount},\${currentSession.tokens},\${currentSession.fileSize},"\${new Date(currentSession.created).toISOString()}","\${new Date(currentSession.lastModified).toISOString()}","\${currentSession.status}"\\n\\n\`;
|
|
1147
|
+
|
|
1148
|
+
csvContent += 'Message #,Role,Content\\n';
|
|
1149
|
+
|
|
1150
|
+
// Add conversation history if loaded
|
|
1151
|
+
fetch(\`/api/session/\${currentSession.id}\`)
|
|
1152
|
+
.then(response => response.json())
|
|
1153
|
+
.then(sessionData => {
|
|
1154
|
+
if (sessionData.messages) {
|
|
1155
|
+
sessionData.messages.forEach((message, index) => {
|
|
1156
|
+
const content = (message.content || 'no content').replace(/"/g, '""');
|
|
1157
|
+
csvContent += \`\${index + 1},"\${message.role}","\${content}"\\n\`;
|
|
1158
|
+
});
|
|
1159
|
+
}
|
|
1160
|
+
|
|
1161
|
+
// Download CSV
|
|
1162
|
+
const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' });
|
|
1163
|
+
const link = document.createElement('a');
|
|
1164
|
+
const url = URL.createObjectURL(blob);
|
|
1165
|
+
link.setAttribute('href', url);
|
|
1166
|
+
link.setAttribute('download', \`claude-session-\${currentSession.id.substring(0, 8)}.csv\`);
|
|
1167
|
+
link.style.visibility = 'hidden';
|
|
1168
|
+
document.body.appendChild(link);
|
|
1169
|
+
link.click();
|
|
1170
|
+
document.body.removeChild(link);
|
|
1171
|
+
})
|
|
1172
|
+
.catch(error => {
|
|
1173
|
+
console.error('Failed to export CSV:', error);
|
|
1174
|
+
alert('Failed to export CSV. Please try again.');
|
|
1175
|
+
});
|
|
1176
|
+
}
|
|
1177
|
+
|
|
1178
|
+
function refreshSessionDetail() {
|
|
1179
|
+
if (currentSession) {
|
|
1180
|
+
loadConversationHistory(currentSession);
|
|
1181
|
+
}
|
|
753
1182
|
}
|
|
754
1183
|
|
|
755
1184
|
// Manual refresh function
|