claude-code-templates 1.5.17 → 1.6.1
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-web/index.html +539 -6
- package/src/analytics.js +638 -9
- package/src/index.js +36 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-code-templates",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.6.1",
|
|
4
4
|
"description": "CLI tool to setup Claude Code configurations with framework-specific commands, automation hooks and MCP Servers for your projects",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
<meta charset="UTF-8">
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
6
|
<title>Claude Code Analytics - Terminal</title>
|
|
7
|
+
<script src="https://cdn.jsdelivr.net/npm/chart.js@3.9.1/dist/chart.min.js"></script>
|
|
7
8
|
<style>
|
|
8
9
|
* {
|
|
9
10
|
margin: 0;
|
|
@@ -120,6 +121,111 @@
|
|
|
120
121
|
margin-top: 2px;
|
|
121
122
|
}
|
|
122
123
|
|
|
124
|
+
.chart-controls {
|
|
125
|
+
display: flex;
|
|
126
|
+
align-items: center;
|
|
127
|
+
justify-content: space-between;
|
|
128
|
+
gap: 16px;
|
|
129
|
+
margin: 20px 0;
|
|
130
|
+
padding: 12px 0;
|
|
131
|
+
border-top: 1px solid #21262d;
|
|
132
|
+
border-bottom: 1px solid #21262d;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
.chart-controls-left {
|
|
136
|
+
display: flex;
|
|
137
|
+
align-items: center;
|
|
138
|
+
gap: 16px;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
.chart-controls-right {
|
|
142
|
+
display: flex;
|
|
143
|
+
align-items: center;
|
|
144
|
+
gap: 12px;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
.date-control {
|
|
148
|
+
display: flex;
|
|
149
|
+
align-items: center;
|
|
150
|
+
gap: 8px;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
.date-label {
|
|
154
|
+
color: #7d8590;
|
|
155
|
+
font-size: 0.875rem;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
.date-input {
|
|
159
|
+
background: #21262d;
|
|
160
|
+
border: 1px solid #30363d;
|
|
161
|
+
color: #c9d1d9;
|
|
162
|
+
padding: 6px 12px;
|
|
163
|
+
border-radius: 4px;
|
|
164
|
+
font-family: inherit;
|
|
165
|
+
font-size: 0.875rem;
|
|
166
|
+
cursor: pointer;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
.date-input:focus {
|
|
170
|
+
outline: none;
|
|
171
|
+
border-color: #d57455;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
.refresh-btn {
|
|
175
|
+
background: none;
|
|
176
|
+
border: 1px solid #30363d;
|
|
177
|
+
color: #7d8590;
|
|
178
|
+
padding: 6px 12px;
|
|
179
|
+
border-radius: 4px;
|
|
180
|
+
cursor: pointer;
|
|
181
|
+
font-family: inherit;
|
|
182
|
+
font-size: 0.875rem;
|
|
183
|
+
transition: all 0.2s ease;
|
|
184
|
+
display: flex;
|
|
185
|
+
align-items: center;
|
|
186
|
+
gap: 6px;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
.refresh-btn:hover {
|
|
190
|
+
border-color: #d57455;
|
|
191
|
+
color: #d57455;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
.refresh-btn.loading {
|
|
195
|
+
opacity: 0.6;
|
|
196
|
+
cursor: not-allowed;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
.charts-container {
|
|
200
|
+
display: grid;
|
|
201
|
+
grid-template-columns: 2fr 1fr;
|
|
202
|
+
gap: 30px;
|
|
203
|
+
margin: 20px 0 30px 0;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
.chart-card {
|
|
207
|
+
background: #161b22;
|
|
208
|
+
border: 1px solid #30363d;
|
|
209
|
+
border-radius: 6px;
|
|
210
|
+
padding: 20px;
|
|
211
|
+
position: relative;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
.chart-title {
|
|
215
|
+
color: #d57455;
|
|
216
|
+
font-size: 0.875rem;
|
|
217
|
+
text-transform: uppercase;
|
|
218
|
+
margin-bottom: 16px;
|
|
219
|
+
display: flex;
|
|
220
|
+
align-items: center;
|
|
221
|
+
gap: 8px;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
.chart-canvas {
|
|
225
|
+
width: 100% !important;
|
|
226
|
+
height: 200px !important;
|
|
227
|
+
}
|
|
228
|
+
|
|
123
229
|
.filter-bar {
|
|
124
230
|
display: flex;
|
|
125
231
|
align-items: center;
|
|
@@ -190,6 +296,23 @@
|
|
|
190
296
|
.session-id {
|
|
191
297
|
color: #d57455;
|
|
192
298
|
font-family: monospace;
|
|
299
|
+
display: flex;
|
|
300
|
+
align-items: center;
|
|
301
|
+
gap: 6px;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
.process-indicator {
|
|
305
|
+
display: inline-block;
|
|
306
|
+
width: 6px;
|
|
307
|
+
height: 6px;
|
|
308
|
+
background: #3fb950;
|
|
309
|
+
border-radius: 50%;
|
|
310
|
+
animation: pulse 2s infinite;
|
|
311
|
+
cursor: help;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
.process-indicator.orphan {
|
|
315
|
+
background: #f85149;
|
|
193
316
|
}
|
|
194
317
|
|
|
195
318
|
.session-id-container {
|
|
@@ -587,6 +710,35 @@
|
|
|
587
710
|
gap: 20px;
|
|
588
711
|
}
|
|
589
712
|
|
|
713
|
+
.chart-controls {
|
|
714
|
+
flex-direction: column;
|
|
715
|
+
gap: 12px;
|
|
716
|
+
align-items: stretch;
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
.chart-controls-left {
|
|
720
|
+
flex-direction: column;
|
|
721
|
+
gap: 12px;
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
.chart-controls-right {
|
|
725
|
+
justify-content: center;
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
.charts-container {
|
|
729
|
+
grid-template-columns: 1fr;
|
|
730
|
+
gap: 20px;
|
|
731
|
+
margin: 20px 0;
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
.chart-card {
|
|
735
|
+
padding: 16px;
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
.chart-canvas {
|
|
739
|
+
height: 180px !important;
|
|
740
|
+
}
|
|
741
|
+
|
|
590
742
|
.filter-bar {
|
|
591
743
|
flex-direction: column;
|
|
592
744
|
align-items: flex-start;
|
|
@@ -664,6 +816,40 @@
|
|
|
664
816
|
</div>
|
|
665
817
|
</div>
|
|
666
818
|
|
|
819
|
+
<div class="chart-controls">
|
|
820
|
+
<div class="chart-controls-left">
|
|
821
|
+
<div class="date-control">
|
|
822
|
+
<span class="date-label">from:</span>
|
|
823
|
+
<input type="date" id="dateFrom" class="date-input">
|
|
824
|
+
</div>
|
|
825
|
+
<div class="date-control">
|
|
826
|
+
<span class="date-label">to:</span>
|
|
827
|
+
<input type="date" id="dateTo" class="date-input">
|
|
828
|
+
</div>
|
|
829
|
+
</div>
|
|
830
|
+
<div class="chart-controls-right">
|
|
831
|
+
<button class="refresh-btn" onclick="refreshCharts()" id="refreshBtn">
|
|
832
|
+
🔄 refresh charts
|
|
833
|
+
</button>
|
|
834
|
+
</div>
|
|
835
|
+
</div>
|
|
836
|
+
|
|
837
|
+
<div class="charts-container">
|
|
838
|
+
<div class="chart-card">
|
|
839
|
+
<div class="chart-title">
|
|
840
|
+
📊 token usage over time
|
|
841
|
+
</div>
|
|
842
|
+
<canvas id="tokenChart" class="chart-canvas"></canvas>
|
|
843
|
+
</div>
|
|
844
|
+
|
|
845
|
+
<div class="chart-card">
|
|
846
|
+
<div class="chart-title">
|
|
847
|
+
🎯 project activity distribution
|
|
848
|
+
</div>
|
|
849
|
+
<canvas id="projectChart" class="chart-canvas"></canvas>
|
|
850
|
+
</div>
|
|
851
|
+
</div>
|
|
852
|
+
|
|
667
853
|
<div class="filter-bar">
|
|
668
854
|
<span class="filter-label">filter conversations:</span>
|
|
669
855
|
<div class="filter-buttons">
|
|
@@ -730,6 +916,9 @@
|
|
|
730
916
|
let allConversations = [];
|
|
731
917
|
let currentFilter = 'active';
|
|
732
918
|
let currentSession = null;
|
|
919
|
+
let tokenChart = null;
|
|
920
|
+
let projectChart = null;
|
|
921
|
+
let allData = null;
|
|
733
922
|
|
|
734
923
|
async function loadData() {
|
|
735
924
|
try {
|
|
@@ -746,6 +935,15 @@
|
|
|
746
935
|
|
|
747
936
|
updateStats(data.summary);
|
|
748
937
|
allConversations = data.conversations;
|
|
938
|
+
allData = data; // Store data globally for access
|
|
939
|
+
window.allData = data; // Keep for backward compatibility
|
|
940
|
+
|
|
941
|
+
// Initialize date inputs on first load
|
|
942
|
+
if (!document.getElementById('dateFrom').value) {
|
|
943
|
+
initializeDateInputs();
|
|
944
|
+
}
|
|
945
|
+
|
|
946
|
+
updateCharts(data);
|
|
749
947
|
updateSessionsTable();
|
|
750
948
|
|
|
751
949
|
} catch (error) {
|
|
@@ -769,6 +967,296 @@
|
|
|
769
967
|
}
|
|
770
968
|
}
|
|
771
969
|
|
|
970
|
+
function initializeDateInputs() {
|
|
971
|
+
const today = new Date();
|
|
972
|
+
const sevenDaysAgo = new Date(today);
|
|
973
|
+
sevenDaysAgo.setDate(sevenDaysAgo.getDate() - 7);
|
|
974
|
+
|
|
975
|
+
document.getElementById('dateFrom').value = sevenDaysAgo.toISOString().split('T')[0];
|
|
976
|
+
document.getElementById('dateTo').value = today.toISOString().split('T')[0];
|
|
977
|
+
}
|
|
978
|
+
|
|
979
|
+
function getDateRange() {
|
|
980
|
+
const fromDate = new Date(document.getElementById('dateFrom').value);
|
|
981
|
+
const toDate = new Date(document.getElementById('dateTo').value);
|
|
982
|
+
toDate.setHours(23, 59, 59, 999); // Include the entire end date
|
|
983
|
+
|
|
984
|
+
return { fromDate, toDate };
|
|
985
|
+
}
|
|
986
|
+
|
|
987
|
+
function filterConversationsByDate(conversations) {
|
|
988
|
+
const { fromDate, toDate } = getDateRange();
|
|
989
|
+
|
|
990
|
+
return conversations.filter(conv => {
|
|
991
|
+
const convDate = new Date(conv.lastModified);
|
|
992
|
+
return convDate >= fromDate && convDate <= toDate;
|
|
993
|
+
});
|
|
994
|
+
}
|
|
995
|
+
|
|
996
|
+
function updateCharts(data) {
|
|
997
|
+
// Wait for Chart.js to load before creating charts
|
|
998
|
+
if (typeof Chart === 'undefined') {
|
|
999
|
+
console.log('Chart.js not loaded yet, retrying in 100ms...');
|
|
1000
|
+
setTimeout(() => updateCharts(data), 100);
|
|
1001
|
+
return;
|
|
1002
|
+
}
|
|
1003
|
+
|
|
1004
|
+
// Use ALL conversations but filter chart display by date range
|
|
1005
|
+
// This maintains the original behavior
|
|
1006
|
+
|
|
1007
|
+
// Update Token Usage Over Time Chart
|
|
1008
|
+
updateTokenChart(data.conversations);
|
|
1009
|
+
|
|
1010
|
+
// Update Project Activity Distribution Chart
|
|
1011
|
+
updateProjectChart(data.conversations);
|
|
1012
|
+
}
|
|
1013
|
+
|
|
1014
|
+
async function refreshCharts() {
|
|
1015
|
+
const refreshBtn = document.getElementById('refreshBtn');
|
|
1016
|
+
refreshBtn.classList.add('loading');
|
|
1017
|
+
refreshBtn.textContent = '🔄 refreshing...';
|
|
1018
|
+
|
|
1019
|
+
try {
|
|
1020
|
+
// Use existing data but re-filter and update charts
|
|
1021
|
+
if (allData) {
|
|
1022
|
+
updateCharts(allData);
|
|
1023
|
+
}
|
|
1024
|
+
} catch (error) {
|
|
1025
|
+
console.error('Error refreshing charts:', error);
|
|
1026
|
+
} finally {
|
|
1027
|
+
refreshBtn.classList.remove('loading');
|
|
1028
|
+
refreshBtn.textContent = '🔄 refresh charts';
|
|
1029
|
+
}
|
|
1030
|
+
}
|
|
1031
|
+
|
|
1032
|
+
function updateTokenChart(conversations) {
|
|
1033
|
+
// Check if Chart.js is available
|
|
1034
|
+
if (typeof Chart === 'undefined') {
|
|
1035
|
+
console.warn('Chart.js not available for updateTokenChart');
|
|
1036
|
+
return;
|
|
1037
|
+
}
|
|
1038
|
+
|
|
1039
|
+
// Prepare data for selected date range
|
|
1040
|
+
const { fromDate, toDate } = getDateRange();
|
|
1041
|
+
const dateRange = [];
|
|
1042
|
+
|
|
1043
|
+
const currentDate = new Date(fromDate);
|
|
1044
|
+
while (currentDate <= toDate) {
|
|
1045
|
+
dateRange.push({
|
|
1046
|
+
date: currentDate.toISOString().split('T')[0],
|
|
1047
|
+
label: currentDate.toLocaleDateString('en-US', { month: 'short', day: 'numeric' }),
|
|
1048
|
+
tokens: 0
|
|
1049
|
+
});
|
|
1050
|
+
currentDate.setDate(currentDate.getDate() + 1);
|
|
1051
|
+
}
|
|
1052
|
+
|
|
1053
|
+
// Aggregate tokens by day
|
|
1054
|
+
conversations.forEach(conv => {
|
|
1055
|
+
const convDate = new Date(conv.lastModified).toISOString().split('T')[0];
|
|
1056
|
+
const dayData = dateRange.find(day => day.date === convDate);
|
|
1057
|
+
if (dayData) {
|
|
1058
|
+
dayData.tokens += conv.tokens;
|
|
1059
|
+
}
|
|
1060
|
+
});
|
|
1061
|
+
|
|
1062
|
+
const ctx = document.getElementById('tokenChart').getContext('2d');
|
|
1063
|
+
|
|
1064
|
+
if (tokenChart) {
|
|
1065
|
+
tokenChart.destroy();
|
|
1066
|
+
}
|
|
1067
|
+
|
|
1068
|
+
tokenChart = new Chart(ctx, {
|
|
1069
|
+
type: 'line',
|
|
1070
|
+
data: {
|
|
1071
|
+
labels: dateRange.map(day => day.label),
|
|
1072
|
+
datasets: [{
|
|
1073
|
+
label: 'Tokens',
|
|
1074
|
+
data: dateRange.map(day => day.tokens),
|
|
1075
|
+
borderColor: '#d57455',
|
|
1076
|
+
backgroundColor: 'rgba(213, 116, 85, 0.1)',
|
|
1077
|
+
borderWidth: 2,
|
|
1078
|
+
pointBackgroundColor: '#d57455',
|
|
1079
|
+
pointBorderColor: '#d57455',
|
|
1080
|
+
pointRadius: 4,
|
|
1081
|
+
pointHoverRadius: 6,
|
|
1082
|
+
fill: true,
|
|
1083
|
+
tension: 0.3
|
|
1084
|
+
}]
|
|
1085
|
+
},
|
|
1086
|
+
options: {
|
|
1087
|
+
responsive: true,
|
|
1088
|
+
maintainAspectRatio: false,
|
|
1089
|
+
plugins: {
|
|
1090
|
+
legend: {
|
|
1091
|
+
display: false
|
|
1092
|
+
},
|
|
1093
|
+
tooltip: {
|
|
1094
|
+
backgroundColor: '#161b22',
|
|
1095
|
+
titleColor: '#d57455',
|
|
1096
|
+
bodyColor: '#c9d1d9',
|
|
1097
|
+
borderColor: '#30363d',
|
|
1098
|
+
borderWidth: 1,
|
|
1099
|
+
titleFont: {
|
|
1100
|
+
family: 'Monaco, Menlo, Ubuntu Mono, monospace',
|
|
1101
|
+
size: 12
|
|
1102
|
+
},
|
|
1103
|
+
bodyFont: {
|
|
1104
|
+
family: 'Monaco, Menlo, Ubuntu Mono, monospace',
|
|
1105
|
+
size: 11
|
|
1106
|
+
},
|
|
1107
|
+
callbacks: {
|
|
1108
|
+
title: function(context) {
|
|
1109
|
+
return context[0].label;
|
|
1110
|
+
},
|
|
1111
|
+
label: function(context) {
|
|
1112
|
+
return `Tokens: ${context.parsed.y.toLocaleString()}`;
|
|
1113
|
+
}
|
|
1114
|
+
}
|
|
1115
|
+
}
|
|
1116
|
+
},
|
|
1117
|
+
interaction: {
|
|
1118
|
+
intersect: false,
|
|
1119
|
+
mode: 'index'
|
|
1120
|
+
},
|
|
1121
|
+
hover: {
|
|
1122
|
+
animationDuration: 200
|
|
1123
|
+
},
|
|
1124
|
+
scales: {
|
|
1125
|
+
x: {
|
|
1126
|
+
grid: {
|
|
1127
|
+
color: '#30363d',
|
|
1128
|
+
borderColor: '#30363d'
|
|
1129
|
+
},
|
|
1130
|
+
ticks: {
|
|
1131
|
+
color: '#7d8590',
|
|
1132
|
+
font: {
|
|
1133
|
+
family: 'Monaco, Menlo, Ubuntu Mono, monospace',
|
|
1134
|
+
size: 11
|
|
1135
|
+
}
|
|
1136
|
+
}
|
|
1137
|
+
},
|
|
1138
|
+
y: {
|
|
1139
|
+
grid: {
|
|
1140
|
+
color: '#30363d',
|
|
1141
|
+
borderColor: '#30363d'
|
|
1142
|
+
},
|
|
1143
|
+
ticks: {
|
|
1144
|
+
color: '#7d8590',
|
|
1145
|
+
font: {
|
|
1146
|
+
family: 'Monaco, Menlo, Ubuntu Mono, monospace',
|
|
1147
|
+
size: 11
|
|
1148
|
+
},
|
|
1149
|
+
callback: function(value) {
|
|
1150
|
+
return value.toLocaleString();
|
|
1151
|
+
}
|
|
1152
|
+
}
|
|
1153
|
+
}
|
|
1154
|
+
}
|
|
1155
|
+
}
|
|
1156
|
+
});
|
|
1157
|
+
}
|
|
1158
|
+
|
|
1159
|
+
function updateProjectChart(conversations) {
|
|
1160
|
+
// Check if Chart.js is available
|
|
1161
|
+
if (typeof Chart === 'undefined') {
|
|
1162
|
+
console.warn('Chart.js not available for updateProjectChart');
|
|
1163
|
+
return;
|
|
1164
|
+
}
|
|
1165
|
+
|
|
1166
|
+
// Aggregate data by project
|
|
1167
|
+
const projectData = {};
|
|
1168
|
+
|
|
1169
|
+
conversations.forEach(conv => {
|
|
1170
|
+
if (!projectData[conv.project]) {
|
|
1171
|
+
projectData[conv.project] = 0;
|
|
1172
|
+
}
|
|
1173
|
+
projectData[conv.project] += conv.tokens;
|
|
1174
|
+
});
|
|
1175
|
+
|
|
1176
|
+
// Get top 5 projects and group others
|
|
1177
|
+
const sortedProjects = Object.entries(projectData)
|
|
1178
|
+
.sort(([,a], [,b]) => b - a)
|
|
1179
|
+
.slice(0, 5);
|
|
1180
|
+
|
|
1181
|
+
const othersTotal = Object.entries(projectData)
|
|
1182
|
+
.slice(5)
|
|
1183
|
+
.reduce((sum, [,tokens]) => sum + tokens, 0);
|
|
1184
|
+
|
|
1185
|
+
if (othersTotal > 0) {
|
|
1186
|
+
sortedProjects.push(['others', othersTotal]);
|
|
1187
|
+
}
|
|
1188
|
+
|
|
1189
|
+
// Terminal-style colors
|
|
1190
|
+
const colors = [
|
|
1191
|
+
'#d57455', // Orange
|
|
1192
|
+
'#3fb950', // Green
|
|
1193
|
+
'#a5d6ff', // Blue
|
|
1194
|
+
'#f97316', // Orange variant
|
|
1195
|
+
'#c9d1d9', // Light gray
|
|
1196
|
+
'#7d8590' // Gray
|
|
1197
|
+
];
|
|
1198
|
+
|
|
1199
|
+
const ctx = document.getElementById('projectChart').getContext('2d');
|
|
1200
|
+
|
|
1201
|
+
if (projectChart) {
|
|
1202
|
+
projectChart.destroy();
|
|
1203
|
+
}
|
|
1204
|
+
|
|
1205
|
+
projectChart = new Chart(ctx, {
|
|
1206
|
+
type: 'doughnut',
|
|
1207
|
+
data: {
|
|
1208
|
+
labels: sortedProjects.map(([project]) => project),
|
|
1209
|
+
datasets: [{
|
|
1210
|
+
data: sortedProjects.map(([,tokens]) => tokens),
|
|
1211
|
+
backgroundColor: colors.slice(0, sortedProjects.length),
|
|
1212
|
+
borderColor: '#161b22',
|
|
1213
|
+
borderWidth: 2,
|
|
1214
|
+
hoverBorderWidth: 3
|
|
1215
|
+
}]
|
|
1216
|
+
},
|
|
1217
|
+
options: {
|
|
1218
|
+
responsive: true,
|
|
1219
|
+
maintainAspectRatio: false,
|
|
1220
|
+
plugins: {
|
|
1221
|
+
legend: {
|
|
1222
|
+
position: 'bottom',
|
|
1223
|
+
labels: {
|
|
1224
|
+
color: '#7d8590',
|
|
1225
|
+
font: {
|
|
1226
|
+
family: 'Monaco, Menlo, Ubuntu Mono, monospace',
|
|
1227
|
+
size: 10
|
|
1228
|
+
},
|
|
1229
|
+
padding: 15,
|
|
1230
|
+
usePointStyle: true,
|
|
1231
|
+
pointStyle: 'circle'
|
|
1232
|
+
}
|
|
1233
|
+
},
|
|
1234
|
+
tooltip: {
|
|
1235
|
+
backgroundColor: '#161b22',
|
|
1236
|
+
titleColor: '#d57455',
|
|
1237
|
+
bodyColor: '#c9d1d9',
|
|
1238
|
+
borderColor: '#30363d',
|
|
1239
|
+
borderWidth: 1,
|
|
1240
|
+
titleFont: {
|
|
1241
|
+
family: 'Monaco, Menlo, Ubuntu Mono, monospace'
|
|
1242
|
+
},
|
|
1243
|
+
bodyFont: {
|
|
1244
|
+
family: 'Monaco, Menlo, Ubuntu Mono, monospace'
|
|
1245
|
+
},
|
|
1246
|
+
callbacks: {
|
|
1247
|
+
label: function(context) {
|
|
1248
|
+
const total = context.dataset.data.reduce((sum, value) => sum + value, 0);
|
|
1249
|
+
const percentage = ((context.parsed / total) * 100).toFixed(1);
|
|
1250
|
+
return `${context.label}: ${context.parsed.toLocaleString()} tokens (${percentage}%)`;
|
|
1251
|
+
}
|
|
1252
|
+
}
|
|
1253
|
+
}
|
|
1254
|
+
},
|
|
1255
|
+
cutout: '60%'
|
|
1256
|
+
}
|
|
1257
|
+
});
|
|
1258
|
+
}
|
|
1259
|
+
|
|
772
1260
|
function updateSessionsTable() {
|
|
773
1261
|
const tableBody = document.getElementById('sessionsTable');
|
|
774
1262
|
const noSessionsDiv = document.getElementById('noSessions');
|
|
@@ -791,7 +1279,10 @@
|
|
|
791
1279
|
<tr onclick="showSessionDetail('${conv.id}')" style="cursor: pointer;">
|
|
792
1280
|
<td>
|
|
793
1281
|
<div class="session-id-container">
|
|
794
|
-
<div class="session-id"
|
|
1282
|
+
<div class="session-id">
|
|
1283
|
+
${conv.id.substring(0, 8)}...
|
|
1284
|
+
${conv.runningProcess ? `<span class="process-indicator" title="Active claude process (PID: ${conv.runningProcess.pid})"></span>` : ''}
|
|
1285
|
+
</div>
|
|
795
1286
|
<div class="status-squares">
|
|
796
1287
|
${generateStatusSquaresHTML(conv.statusSquares || [])}
|
|
797
1288
|
</div>
|
|
@@ -806,6 +1297,33 @@
|
|
|
806
1297
|
<td class="status-${conv.status}">${conv.status}</td>
|
|
807
1298
|
</tr>
|
|
808
1299
|
`).join('');
|
|
1300
|
+
|
|
1301
|
+
// NEW: Add orphan processes (active claude commands without conversation)
|
|
1302
|
+
if (window.allData && window.allData.orphanProcesses && window.allData.orphanProcesses.length > 0) {
|
|
1303
|
+
const orphanRows = window.allData.orphanProcesses.map(process => `
|
|
1304
|
+
<tr style="background: rgba(248, 81, 73, 0.1); cursor: default;">
|
|
1305
|
+
<td>
|
|
1306
|
+
<div class="session-id-container">
|
|
1307
|
+
<div class="session-id">
|
|
1308
|
+
orphan-${process.pid}
|
|
1309
|
+
<span class="process-indicator orphan" title="Orphan claude process (PID: ${process.pid})"></span>
|
|
1310
|
+
</div>
|
|
1311
|
+
</div>
|
|
1312
|
+
</td>
|
|
1313
|
+
<td class="session-project">${process.workingDir}</td>
|
|
1314
|
+
<td class="session-model">Unknown</td>
|
|
1315
|
+
<td class="session-messages">-</td>
|
|
1316
|
+
<td class="session-tokens">-</td>
|
|
1317
|
+
<td class="session-time">Running</td>
|
|
1318
|
+
<td class="conversation-state">Active process</td>
|
|
1319
|
+
<td class="status-active">orphan</td>
|
|
1320
|
+
</tr>
|
|
1321
|
+
`).join('');
|
|
1322
|
+
|
|
1323
|
+
if (currentFilter === 'active' || currentFilter === 'all') {
|
|
1324
|
+
tableBody.innerHTML += orphanRows;
|
|
1325
|
+
}
|
|
1326
|
+
}
|
|
809
1327
|
}
|
|
810
1328
|
|
|
811
1329
|
function formatTime(date) {
|
|
@@ -1172,11 +1690,26 @@
|
|
|
1172
1690
|
}
|
|
1173
1691
|
}
|
|
1174
1692
|
|
|
1175
|
-
//
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1693
|
+
// Wait for DOM and Chart.js to load
|
|
1694
|
+
document.addEventListener('DOMContentLoaded', function() {
|
|
1695
|
+
// Check if Chart.js is loaded
|
|
1696
|
+
function initWhenReady() {
|
|
1697
|
+
if (typeof Chart !== 'undefined') {
|
|
1698
|
+
console.log('Chart.js loaded successfully');
|
|
1699
|
+
loadData();
|
|
1700
|
+
// No automatic refresh - manual only
|
|
1701
|
+
} else {
|
|
1702
|
+
console.log('Waiting for Chart.js to load...');
|
|
1703
|
+
setTimeout(initWhenReady, 100);
|
|
1704
|
+
}
|
|
1705
|
+
}
|
|
1706
|
+
|
|
1707
|
+
initWhenReady();
|
|
1708
|
+
|
|
1709
|
+
// Add event listeners for date inputs
|
|
1710
|
+
document.getElementById('dateFrom').addEventListener('change', refreshCharts);
|
|
1711
|
+
document.getElementById('dateTo').addEventListener('change', refreshCharts);
|
|
1712
|
+
});
|
|
1180
1713
|
|
|
1181
1714
|
// Add keyboard shortcut for refresh (F5 or Ctrl+R)
|
|
1182
1715
|
document.addEventListener('keydown', function(e) {
|