claude-code-templates 1.8.2 → 1.9.0
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
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-code-templates",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.9.0",
|
|
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": {
|
|
@@ -31,7 +31,6 @@
|
|
|
31
31
|
"dev:link": "npm link",
|
|
32
32
|
"dev:unlink": "npm unlink -g claude-code-templates",
|
|
33
33
|
"pretest:commands": "npm run dev:link",
|
|
34
|
-
"prepublishOnly": "echo 'Skipping tests for emergency hotfix'",
|
|
35
34
|
"analytics:start": "node src/analytics.js",
|
|
36
35
|
"analytics:test": "npm run test:analytics"
|
|
37
36
|
},
|
|
@@ -110,6 +110,9 @@ class ConversationAnalyzer {
|
|
|
110
110
|
const tokenUsage = await this.getCachedTokenUsage(filePath, parsedMessages);
|
|
111
111
|
const modelInfo = await this.getCachedModelInfo(filePath, parsedMessages);
|
|
112
112
|
|
|
113
|
+
// Calculate tool usage data with caching
|
|
114
|
+
const toolUsage = await this.getCachedToolUsage(filePath, parsedMessages);
|
|
115
|
+
|
|
113
116
|
const conversation = {
|
|
114
117
|
id: filename.replace('.jsonl', ''),
|
|
115
118
|
filename: filename,
|
|
@@ -121,6 +124,7 @@ class ConversationAnalyzer {
|
|
|
121
124
|
tokens: tokenUsage.total > 0 ? tokenUsage.total : this.estimateTokens(await this.getFileContent(filePath)),
|
|
122
125
|
tokenUsage: tokenUsage,
|
|
123
126
|
modelInfo: modelInfo,
|
|
127
|
+
toolUsage: toolUsage,
|
|
124
128
|
project: projectFromPath || this.extractProjectFromConversation(parsedMessages),
|
|
125
129
|
status: stateCalculator.determineConversationStatus(parsedMessages, stats.mtime),
|
|
126
130
|
conversationState: stateCalculator.determineConversationState(parsedMessages, stats.mtime),
|
|
@@ -279,6 +283,21 @@ class ConversationAnalyzer {
|
|
|
279
283
|
return this.generateStatusSquares(parsedMessages);
|
|
280
284
|
}
|
|
281
285
|
|
|
286
|
+
/**
|
|
287
|
+
* Get cached tool usage analysis
|
|
288
|
+
* @param {string} filepath - File path
|
|
289
|
+
* @param {Array} parsedMessages - Parsed messages array
|
|
290
|
+
* @returns {Promise<Object>} Tool usage data
|
|
291
|
+
*/
|
|
292
|
+
async getCachedToolUsage(filepath, parsedMessages) {
|
|
293
|
+
if (this.dataCache) {
|
|
294
|
+
return await this.dataCache.getCachedToolUsage(filepath, () => {
|
|
295
|
+
return this.extractToolUsage(parsedMessages);
|
|
296
|
+
});
|
|
297
|
+
}
|
|
298
|
+
return this.extractToolUsage(parsedMessages);
|
|
299
|
+
}
|
|
300
|
+
|
|
282
301
|
/**
|
|
283
302
|
* Calculate real token usage from message usage data
|
|
284
303
|
* @param {Array} parsedMessages - Array of parsed message objects
|
|
@@ -387,6 +406,65 @@ class ConversationAnalyzer {
|
|
|
387
406
|
return 'Unknown';
|
|
388
407
|
}
|
|
389
408
|
|
|
409
|
+
/**
|
|
410
|
+
* Extract tool usage statistics from parsed messages
|
|
411
|
+
* @param {Array} parsedMessages - Array of parsed message objects
|
|
412
|
+
* @returns {Object} Tool usage statistics
|
|
413
|
+
*/
|
|
414
|
+
extractToolUsage(parsedMessages) {
|
|
415
|
+
const toolStats = {};
|
|
416
|
+
const toolTimeline = [];
|
|
417
|
+
let totalToolCalls = 0;
|
|
418
|
+
|
|
419
|
+
parsedMessages.forEach(message => {
|
|
420
|
+
if (message.role === 'assistant' && message.content) {
|
|
421
|
+
const content = message.content;
|
|
422
|
+
const timestamp = message.timestamp;
|
|
423
|
+
|
|
424
|
+
// Handle string content with tool indicators
|
|
425
|
+
if (typeof content === 'string') {
|
|
426
|
+
const toolMatches = content.match(/\[Tool:\s*([^\]]+)\]/g);
|
|
427
|
+
if (toolMatches) {
|
|
428
|
+
toolMatches.forEach(match => {
|
|
429
|
+
const toolName = match.replace(/\[Tool:\s*([^\]]+)\]/, '$1').trim();
|
|
430
|
+
toolStats[toolName] = (toolStats[toolName] || 0) + 1;
|
|
431
|
+
totalToolCalls++;
|
|
432
|
+
toolTimeline.push({
|
|
433
|
+
tool: toolName,
|
|
434
|
+
timestamp: timestamp,
|
|
435
|
+
type: 'usage'
|
|
436
|
+
});
|
|
437
|
+
});
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
// Handle array content with tool_use blocks
|
|
442
|
+
if (Array.isArray(content)) {
|
|
443
|
+
content.forEach(block => {
|
|
444
|
+
if (block.type === 'tool_use') {
|
|
445
|
+
const toolName = block.name || 'Unknown Tool';
|
|
446
|
+
toolStats[toolName] = (toolStats[toolName] || 0) + 1;
|
|
447
|
+
totalToolCalls++;
|
|
448
|
+
toolTimeline.push({
|
|
449
|
+
tool: toolName,
|
|
450
|
+
timestamp: timestamp,
|
|
451
|
+
type: 'usage',
|
|
452
|
+
parameters: block.input || {}
|
|
453
|
+
});
|
|
454
|
+
}
|
|
455
|
+
});
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
});
|
|
459
|
+
|
|
460
|
+
return {
|
|
461
|
+
toolStats,
|
|
462
|
+
toolTimeline: toolTimeline.sort((a, b) => new Date(a.timestamp) - new Date(b.timestamp)),
|
|
463
|
+
totalToolCalls,
|
|
464
|
+
uniqueTools: Object.keys(toolStats).length
|
|
465
|
+
};
|
|
466
|
+
}
|
|
467
|
+
|
|
390
468
|
/**
|
|
391
469
|
* Generate status indicators for conversation messages
|
|
392
470
|
* @param {Array} messages - Array of message objects
|
|
@@ -19,6 +19,7 @@ class DataCache {
|
|
|
19
19
|
tokenUsage: new Map(), // filepath -> { usage, timestamp }
|
|
20
20
|
modelInfo: new Map(), // filepath -> { info, timestamp }
|
|
21
21
|
statusSquares: new Map(), // filepath -> { squares, timestamp }
|
|
22
|
+
toolUsage: new Map(), // filepath -> { usage, timestamp }
|
|
22
23
|
|
|
23
24
|
// Expensive computations cache
|
|
24
25
|
sessions: { data: null, timestamp: 0, dependencies: new Set() },
|
|
@@ -319,6 +320,32 @@ class DataCache {
|
|
|
319
320
|
return squares;
|
|
320
321
|
}
|
|
321
322
|
|
|
323
|
+
/**
|
|
324
|
+
* Cache tool usage analysis
|
|
325
|
+
* @param {string} filepath - File path
|
|
326
|
+
* @param {Function} extractFn - Tool usage extraction function
|
|
327
|
+
* @returns {Promise<Object>} Tool usage data
|
|
328
|
+
*/
|
|
329
|
+
async getCachedToolUsage(filepath, extractFn) {
|
|
330
|
+
const cached = this.caches.toolUsage.get(filepath);
|
|
331
|
+
const fileStats = await this.getFileStats(filepath);
|
|
332
|
+
|
|
333
|
+
if (cached && cached.timestamp >= fileStats.mtime.getTime()) {
|
|
334
|
+
this.metrics.hits++;
|
|
335
|
+
return cached.usage;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
this.metrics.misses++;
|
|
339
|
+
const usage = await extractFn();
|
|
340
|
+
|
|
341
|
+
this.caches.toolUsage.set(filepath, {
|
|
342
|
+
usage,
|
|
343
|
+
timestamp: fileStats.mtime.getTime()
|
|
344
|
+
});
|
|
345
|
+
|
|
346
|
+
return usage;
|
|
347
|
+
}
|
|
348
|
+
|
|
322
349
|
/**
|
|
323
350
|
* Smart invalidation based on file changes
|
|
324
351
|
* @param {string} filepath - Path of changed file
|
|
@@ -332,6 +359,7 @@ class DataCache {
|
|
|
332
359
|
this.caches.tokenUsage.delete(filepath);
|
|
333
360
|
this.caches.modelInfo.delete(filepath);
|
|
334
361
|
this.caches.statusSquares.delete(filepath);
|
|
362
|
+
this.caches.toolUsage.delete(filepath);
|
|
335
363
|
this.caches.fileStats.delete(filepath);
|
|
336
364
|
|
|
337
365
|
// Invalidate computations that depend on this file
|
|
@@ -450,6 +478,7 @@ class DataCache {
|
|
|
450
478
|
['tokenUsage', this.caches.tokenUsage],
|
|
451
479
|
['modelInfo', this.caches.modelInfo],
|
|
452
480
|
['statusSquares', this.caches.statusSquares],
|
|
481
|
+
['toolUsage', this.caches.toolUsage],
|
|
453
482
|
['fileStats', this.caches.fileStats],
|
|
454
483
|
['projectStats', this.caches.projectStats]
|
|
455
484
|
];
|
|
@@ -507,6 +536,7 @@ class DataCache {
|
|
|
507
536
|
tokenUsage: this.caches.tokenUsage.size,
|
|
508
537
|
modelInfo: this.caches.modelInfo.size,
|
|
509
538
|
statusSquares: this.caches.statusSquares.size,
|
|
539
|
+
toolUsage: this.caches.toolUsage.size,
|
|
510
540
|
fileStats: this.caches.fileStats.size,
|
|
511
541
|
projectStats: this.caches.projectStats.size,
|
|
512
542
|
},
|
|
@@ -7,6 +7,42 @@
|
|
|
7
7
|
<script src="https://cdn.jsdelivr.net/npm/chart.js@3.9.1/dist/chart.min.js"></script>
|
|
8
8
|
<script src="components/SessionTimer.js"></script>
|
|
9
9
|
<style>
|
|
10
|
+
:root {
|
|
11
|
+
/* Dark theme colors (default) */
|
|
12
|
+
--bg-primary: #0d1117;
|
|
13
|
+
--bg-secondary: #161b22;
|
|
14
|
+
--bg-tertiary: #21262d;
|
|
15
|
+
--border-primary: #30363d;
|
|
16
|
+
--border-secondary: #21262d;
|
|
17
|
+
--text-primary: #c9d1d9;
|
|
18
|
+
--text-secondary: #7d8590;
|
|
19
|
+
--text-accent: #d57455;
|
|
20
|
+
--text-success: #3fb950;
|
|
21
|
+
--text-warning: #f97316;
|
|
22
|
+
--text-error: #f85149;
|
|
23
|
+
--text-info: #a5d6ff;
|
|
24
|
+
--shadow-primary: rgba(0, 0, 0, 0.4);
|
|
25
|
+
--shadow-secondary: rgba(1, 4, 9, 0.85);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
[data-theme="light"] {
|
|
29
|
+
/* Light theme colors */
|
|
30
|
+
--bg-primary: #ffffff;
|
|
31
|
+
--bg-secondary: #f6f8fa;
|
|
32
|
+
--bg-tertiary: #f1f3f4;
|
|
33
|
+
--border-primary: #d0d7de;
|
|
34
|
+
--border-secondary: #e5e5e5;
|
|
35
|
+
--text-primary: #24292f;
|
|
36
|
+
--text-secondary: #656d76;
|
|
37
|
+
--text-accent: #d73a49;
|
|
38
|
+
--text-success: #28a745;
|
|
39
|
+
--text-warning: #f97316;
|
|
40
|
+
--text-error: #d73a49;
|
|
41
|
+
--text-info: #0366d6;
|
|
42
|
+
--shadow-primary: rgba(0, 0, 0, 0.1);
|
|
43
|
+
--shadow-secondary: rgba(0, 0, 0, 0.2);
|
|
44
|
+
}
|
|
45
|
+
|
|
10
46
|
* {
|
|
11
47
|
margin: 0;
|
|
12
48
|
padding: 0;
|
|
@@ -15,10 +51,11 @@
|
|
|
15
51
|
|
|
16
52
|
body {
|
|
17
53
|
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
|
|
18
|
-
background:
|
|
19
|
-
color:
|
|
54
|
+
background: var(--bg-primary);
|
|
55
|
+
color: var(--text-primary);
|
|
20
56
|
min-height: 100vh;
|
|
21
57
|
line-height: 1.4;
|
|
58
|
+
transition: background-color 0.3s ease, color 0.3s ease;
|
|
22
59
|
}
|
|
23
60
|
|
|
24
61
|
.terminal {
|
|
@@ -28,14 +65,14 @@
|
|
|
28
65
|
}
|
|
29
66
|
|
|
30
67
|
.terminal-header {
|
|
31
|
-
border-bottom: 1px solid
|
|
68
|
+
border-bottom: 1px solid var(--border-primary);
|
|
32
69
|
padding-bottom: 20px;
|
|
33
70
|
margin-bottom: 20px;
|
|
34
71
|
position: relative;
|
|
35
72
|
}
|
|
36
73
|
|
|
37
74
|
.terminal-title {
|
|
38
|
-
color:
|
|
75
|
+
color: var(--text-accent);
|
|
39
76
|
font-size: 1.25rem;
|
|
40
77
|
font-weight: normal;
|
|
41
78
|
display: flex;
|
|
@@ -47,7 +84,7 @@
|
|
|
47
84
|
width: 8px;
|
|
48
85
|
height: 8px;
|
|
49
86
|
border-radius: 50%;
|
|
50
|
-
background:
|
|
87
|
+
background: var(--text-success);
|
|
51
88
|
animation: pulse 2s infinite;
|
|
52
89
|
}
|
|
53
90
|
|
|
@@ -57,18 +94,94 @@
|
|
|
57
94
|
}
|
|
58
95
|
|
|
59
96
|
.terminal-subtitle {
|
|
60
|
-
color:
|
|
97
|
+
color: var(--text-secondary);
|
|
61
98
|
font-size: 0.875rem;
|
|
62
99
|
margin-top: 4px;
|
|
63
100
|
}
|
|
64
101
|
|
|
65
|
-
.
|
|
102
|
+
.header-actions {
|
|
66
103
|
position: absolute;
|
|
67
104
|
top: 0;
|
|
68
105
|
right: 0;
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
106
|
+
display: flex;
|
|
107
|
+
gap: 8px;
|
|
108
|
+
align-items: center;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
.theme-switch-container {
|
|
112
|
+
display: flex;
|
|
113
|
+
align-items: center;
|
|
114
|
+
font-family: inherit;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
.theme-switch {
|
|
118
|
+
position: relative;
|
|
119
|
+
cursor: pointer;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
.theme-switch-track {
|
|
123
|
+
position: relative;
|
|
124
|
+
width: 48px;
|
|
125
|
+
height: 24px;
|
|
126
|
+
background: var(--bg-tertiary);
|
|
127
|
+
border: 1px solid var(--border-primary);
|
|
128
|
+
border-radius: 3px;
|
|
129
|
+
transition: all 0.3s ease;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
.theme-switch-track:hover {
|
|
133
|
+
border-color: var(--text-accent);
|
|
134
|
+
background: var(--border-primary);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
.theme-switch-thumb {
|
|
138
|
+
position: absolute;
|
|
139
|
+
top: 2px;
|
|
140
|
+
left: 2px;
|
|
141
|
+
width: 18px;
|
|
142
|
+
height: 18px;
|
|
143
|
+
background: var(--text-accent);
|
|
144
|
+
border-radius: 2px;
|
|
145
|
+
display: flex;
|
|
146
|
+
align-items: center;
|
|
147
|
+
justify-content: center;
|
|
148
|
+
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
|
149
|
+
transform: translateX(0);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
.theme-switch-thumb.light {
|
|
153
|
+
transform: translateX(22px);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
.theme-switch-icon {
|
|
157
|
+
font-size: 0.7rem;
|
|
158
|
+
color: var(--bg-primary);
|
|
159
|
+
transition: all 0.3s ease;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
.theme-switch-track::before {
|
|
164
|
+
content: '';
|
|
165
|
+
position: absolute;
|
|
166
|
+
top: -1px;
|
|
167
|
+
left: -1px;
|
|
168
|
+
right: -1px;
|
|
169
|
+
bottom: -1px;
|
|
170
|
+
background: linear-gradient(45deg, transparent, var(--text-accent));
|
|
171
|
+
border-radius: 3px;
|
|
172
|
+
opacity: 0;
|
|
173
|
+
transition: opacity 0.3s ease;
|
|
174
|
+
z-index: -1;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
.theme-switch-track:hover::before {
|
|
178
|
+
opacity: 0.1;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
.github-star-btn {
|
|
182
|
+
background: var(--bg-tertiary);
|
|
183
|
+
border: 1px solid var(--border-primary);
|
|
184
|
+
color: var(--text-primary);
|
|
72
185
|
padding: 8px 12px;
|
|
73
186
|
border-radius: 6px;
|
|
74
187
|
text-decoration: none;
|
|
@@ -82,9 +195,9 @@
|
|
|
82
195
|
}
|
|
83
196
|
|
|
84
197
|
.github-star-btn:hover {
|
|
85
|
-
border-color:
|
|
86
|
-
background:
|
|
87
|
-
color:
|
|
198
|
+
border-color: var(--text-accent);
|
|
199
|
+
background: var(--border-primary);
|
|
200
|
+
color: var(--text-accent);
|
|
88
201
|
text-decoration: none;
|
|
89
202
|
}
|
|
90
203
|
|
|
@@ -710,7 +823,7 @@
|
|
|
710
823
|
|
|
711
824
|
.charts-container {
|
|
712
825
|
display: grid;
|
|
713
|
-
grid-template-columns:
|
|
826
|
+
grid-template-columns: 1fr 1fr;
|
|
714
827
|
gap: 30px;
|
|
715
828
|
margin: 20px 0 30px 0;
|
|
716
829
|
}
|
|
@@ -738,6 +851,85 @@
|
|
|
738
851
|
height: 200px !important;
|
|
739
852
|
}
|
|
740
853
|
|
|
854
|
+
.tool-summary {
|
|
855
|
+
padding: 20px;
|
|
856
|
+
height: 200px;
|
|
857
|
+
display: flex;
|
|
858
|
+
flex-direction: column;
|
|
859
|
+
gap: 12px;
|
|
860
|
+
overflow-y: auto;
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
.tool-stat {
|
|
864
|
+
display: flex;
|
|
865
|
+
justify-content: space-between;
|
|
866
|
+
align-items: center;
|
|
867
|
+
padding: 8px 12px;
|
|
868
|
+
background: var(--bg-tertiary);
|
|
869
|
+
border: 1px solid var(--border-secondary);
|
|
870
|
+
border-radius: 4px;
|
|
871
|
+
transition: all 0.2s ease;
|
|
872
|
+
font-size: 0.8rem;
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
.tool-stat:hover {
|
|
876
|
+
background: var(--bg-secondary);
|
|
877
|
+
border-color: var(--border-primary);
|
|
878
|
+
}
|
|
879
|
+
|
|
880
|
+
.tool-stat-label {
|
|
881
|
+
color: var(--text-secondary);
|
|
882
|
+
font-weight: 500;
|
|
883
|
+
}
|
|
884
|
+
|
|
885
|
+
.tool-stat-value {
|
|
886
|
+
color: var(--text-primary);
|
|
887
|
+
font-weight: 600;
|
|
888
|
+
}
|
|
889
|
+
|
|
890
|
+
.tool-stat-accent {
|
|
891
|
+
color: var(--text-accent);
|
|
892
|
+
}
|
|
893
|
+
|
|
894
|
+
.tool-top-tool {
|
|
895
|
+
display: flex;
|
|
896
|
+
align-items: center;
|
|
897
|
+
gap: 8px;
|
|
898
|
+
padding: 12px;
|
|
899
|
+
background: var(--bg-tertiary);
|
|
900
|
+
border: 1px solid var(--border-secondary);
|
|
901
|
+
border-radius: 4px;
|
|
902
|
+
margin-top: auto;
|
|
903
|
+
}
|
|
904
|
+
|
|
905
|
+
.tool-icon {
|
|
906
|
+
width: 24px;
|
|
907
|
+
height: 24px;
|
|
908
|
+
background: var(--text-accent);
|
|
909
|
+
border-radius: 4px;
|
|
910
|
+
display: flex;
|
|
911
|
+
align-items: center;
|
|
912
|
+
justify-content: center;
|
|
913
|
+
font-size: 12px;
|
|
914
|
+
color: white;
|
|
915
|
+
}
|
|
916
|
+
|
|
917
|
+
.tool-info {
|
|
918
|
+
flex: 1;
|
|
919
|
+
}
|
|
920
|
+
|
|
921
|
+
.tool-name {
|
|
922
|
+
color: var(--text-primary);
|
|
923
|
+
font-size: 0.8rem;
|
|
924
|
+
font-weight: 600;
|
|
925
|
+
margin-bottom: 2px;
|
|
926
|
+
}
|
|
927
|
+
|
|
928
|
+
.tool-usage {
|
|
929
|
+
color: var(--text-secondary);
|
|
930
|
+
font-size: 0.7rem;
|
|
931
|
+
}
|
|
932
|
+
|
|
741
933
|
.filter-bar {
|
|
742
934
|
display: flex;
|
|
743
935
|
align-items: center;
|
|
@@ -1317,12 +1509,20 @@
|
|
|
1317
1509
|
padding: 6px 8px;
|
|
1318
1510
|
}
|
|
1319
1511
|
|
|
1320
|
-
.
|
|
1512
|
+
.header-actions {
|
|
1321
1513
|
position: relative;
|
|
1322
1514
|
margin-top: 12px;
|
|
1323
1515
|
align-self: flex-start;
|
|
1516
|
+
gap: 12px;
|
|
1324
1517
|
}
|
|
1325
1518
|
|
|
1519
|
+
.github-star-btn {
|
|
1520
|
+
position: relative;
|
|
1521
|
+
margin-top: 0;
|
|
1522
|
+
align-self: flex-start;
|
|
1523
|
+
}
|
|
1524
|
+
|
|
1525
|
+
|
|
1326
1526
|
.terminal-header {
|
|
1327
1527
|
display: flex;
|
|
1328
1528
|
flex-direction: column;
|
|
@@ -1340,10 +1540,21 @@
|
|
|
1340
1540
|
<div class="terminal-subtitle">real-time monitoring dashboard</div>
|
|
1341
1541
|
<div class="terminal-subtitle" id="lastUpdate"></div>
|
|
1342
1542
|
|
|
1343
|
-
<
|
|
1344
|
-
<
|
|
1345
|
-
|
|
1346
|
-
|
|
1543
|
+
<div class="header-actions">
|
|
1544
|
+
<div class="theme-switch-container" title="Toggle light/dark theme">
|
|
1545
|
+
<div class="theme-switch" id="themeSwitch">
|
|
1546
|
+
<div class="theme-switch-track">
|
|
1547
|
+
<div class="theme-switch-thumb" id="themeSwitchThumb">
|
|
1548
|
+
<span class="theme-switch-icon">🌙</span>
|
|
1549
|
+
</div>
|
|
1550
|
+
</div>
|
|
1551
|
+
</div>
|
|
1552
|
+
</div>
|
|
1553
|
+
<a href="https://github.com/davila7/claude-code-templates" target="_blank" class="github-star-btn" title="Give us a star on GitHub to support the project!">
|
|
1554
|
+
<span class="star-icon">⭐</span>
|
|
1555
|
+
<span>Star on GitHub</span>
|
|
1556
|
+
</a>
|
|
1557
|
+
</div>
|
|
1347
1558
|
</div>
|
|
1348
1559
|
|
|
1349
1560
|
<div id="loading" class="loading">
|
|
@@ -1453,6 +1664,22 @@
|
|
|
1453
1664
|
</div>
|
|
1454
1665
|
<canvas id="projectChart" class="chart-canvas"></canvas>
|
|
1455
1666
|
</div>
|
|
1667
|
+
|
|
1668
|
+
<div class="chart-card">
|
|
1669
|
+
<div class="chart-title">
|
|
1670
|
+
🛠️ tool usage trends
|
|
1671
|
+
</div>
|
|
1672
|
+
<canvas id="toolChart" class="chart-canvas"></canvas>
|
|
1673
|
+
</div>
|
|
1674
|
+
|
|
1675
|
+
<div class="chart-card">
|
|
1676
|
+
<div class="chart-title">
|
|
1677
|
+
⚡ tool activity summary
|
|
1678
|
+
</div>
|
|
1679
|
+
<div id="toolSummary" class="tool-summary">
|
|
1680
|
+
<!-- Tool summary will be loaded here -->
|
|
1681
|
+
</div>
|
|
1682
|
+
</div>
|
|
1456
1683
|
</div>
|
|
1457
1684
|
|
|
1458
1685
|
<div class="filter-bar">
|
|
@@ -1530,6 +1757,7 @@
|
|
|
1530
1757
|
let currentSession = null;
|
|
1531
1758
|
let tokenChart = null;
|
|
1532
1759
|
let projectChart = null;
|
|
1760
|
+
let toolChart = null;
|
|
1533
1761
|
let allData = null;
|
|
1534
1762
|
let notificationsEnabled = false;
|
|
1535
1763
|
let previousConversationStates = new Map();
|
|
@@ -1899,6 +2127,12 @@
|
|
|
1899
2127
|
|
|
1900
2128
|
// Update Project Activity Distribution Chart
|
|
1901
2129
|
updateProjectChart(data.conversations);
|
|
2130
|
+
|
|
2131
|
+
// Update Tool Usage Trends Chart
|
|
2132
|
+
updateToolChart(data.conversations);
|
|
2133
|
+
|
|
2134
|
+
// Update Tool Activity Summary
|
|
2135
|
+
updateToolSummary(data.conversations);
|
|
1902
2136
|
}
|
|
1903
2137
|
|
|
1904
2138
|
async function refreshCharts() {
|
|
@@ -1962,11 +2196,11 @@
|
|
|
1962
2196
|
datasets: [{
|
|
1963
2197
|
label: 'Tokens',
|
|
1964
2198
|
data: dateRange.map(day => day.tokens),
|
|
1965
|
-
borderColor: '
|
|
1966
|
-
backgroundColor: '
|
|
2199
|
+
borderColor: getComputedStyle(document.documentElement).getPropertyValue('--text-accent').trim(),
|
|
2200
|
+
backgroundColor: getComputedStyle(document.documentElement).getPropertyValue('--text-accent').trim() + '20',
|
|
1967
2201
|
borderWidth: 2,
|
|
1968
|
-
pointBackgroundColor: '
|
|
1969
|
-
pointBorderColor: '
|
|
2202
|
+
pointBackgroundColor: getComputedStyle(document.documentElement).getPropertyValue('--text-accent').trim(),
|
|
2203
|
+
pointBorderColor: getComputedStyle(document.documentElement).getPropertyValue('--text-accent').trim(),
|
|
1970
2204
|
pointRadius: 4,
|
|
1971
2205
|
pointHoverRadius: 6,
|
|
1972
2206
|
fill: true,
|
|
@@ -2076,14 +2310,14 @@
|
|
|
2076
2310
|
sortedProjects.push(['others', othersTotal]);
|
|
2077
2311
|
}
|
|
2078
2312
|
|
|
2079
|
-
// Terminal-style colors
|
|
2313
|
+
// Terminal-style colors using CSS variables
|
|
2080
2314
|
const colors = [
|
|
2081
|
-
'
|
|
2082
|
-
'
|
|
2083
|
-
'
|
|
2084
|
-
'
|
|
2085
|
-
'
|
|
2086
|
-
'
|
|
2315
|
+
getComputedStyle(document.documentElement).getPropertyValue('--text-accent').trim(),
|
|
2316
|
+
getComputedStyle(document.documentElement).getPropertyValue('--text-success').trim(),
|
|
2317
|
+
getComputedStyle(document.documentElement).getPropertyValue('--text-info').trim(),
|
|
2318
|
+
getComputedStyle(document.documentElement).getPropertyValue('--text-warning').trim(),
|
|
2319
|
+
getComputedStyle(document.documentElement).getPropertyValue('--text-primary').trim(),
|
|
2320
|
+
getComputedStyle(document.documentElement).getPropertyValue('--text-secondary').trim()
|
|
2087
2321
|
];
|
|
2088
2322
|
|
|
2089
2323
|
const ctx = document.getElementById('projectChart').getContext('2d');
|
|
@@ -2099,7 +2333,7 @@
|
|
|
2099
2333
|
datasets: [{
|
|
2100
2334
|
data: sortedProjects.map(([,tokens]) => tokens),
|
|
2101
2335
|
backgroundColor: colors.slice(0, sortedProjects.length),
|
|
2102
|
-
borderColor: '
|
|
2336
|
+
borderColor: getComputedStyle(document.documentElement).getPropertyValue('--bg-secondary').trim(),
|
|
2103
2337
|
borderWidth: 2,
|
|
2104
2338
|
hoverBorderWidth: 3
|
|
2105
2339
|
}]
|
|
@@ -2147,6 +2381,329 @@
|
|
|
2147
2381
|
});
|
|
2148
2382
|
}
|
|
2149
2383
|
|
|
2384
|
+
function updateToolChart(conversations) {
|
|
2385
|
+
// Check if Chart.js is available
|
|
2386
|
+
if (typeof Chart === 'undefined') {
|
|
2387
|
+
console.warn('Chart.js not available for updateToolChart');
|
|
2388
|
+
return;
|
|
2389
|
+
}
|
|
2390
|
+
|
|
2391
|
+
// Apply date filtering to conversations first
|
|
2392
|
+
const { fromDate, toDate } = getDateRange();
|
|
2393
|
+
const filteredConversations = conversations.filter(conv => {
|
|
2394
|
+
const convDate = new Date(conv.lastModified);
|
|
2395
|
+
return convDate >= fromDate && convDate <= toDate;
|
|
2396
|
+
});
|
|
2397
|
+
|
|
2398
|
+
// Aggregate tool usage across filtered conversations
|
|
2399
|
+
const toolTimeline = [];
|
|
2400
|
+
const toolStats = {};
|
|
2401
|
+
|
|
2402
|
+
filteredConversations.forEach(conv => {
|
|
2403
|
+
if (conv.toolUsage && conv.toolUsage.toolTimeline) {
|
|
2404
|
+
conv.toolUsage.toolTimeline.forEach(entry => {
|
|
2405
|
+
const entryDate = new Date(entry.timestamp);
|
|
2406
|
+
// Only include tool usage within date range
|
|
2407
|
+
if (entryDate >= fromDate && entryDate <= toDate) {
|
|
2408
|
+
toolTimeline.push({
|
|
2409
|
+
tool: entry.tool,
|
|
2410
|
+
timestamp: entryDate,
|
|
2411
|
+
date: entryDate.toDateString()
|
|
2412
|
+
});
|
|
2413
|
+
|
|
2414
|
+
toolStats[entry.tool] = (toolStats[entry.tool] || 0) + 1;
|
|
2415
|
+
}
|
|
2416
|
+
});
|
|
2417
|
+
}
|
|
2418
|
+
});
|
|
2419
|
+
|
|
2420
|
+
const ctx = document.getElementById('toolChart').getContext('2d');
|
|
2421
|
+
|
|
2422
|
+
if (toolChart) {
|
|
2423
|
+
toolChart.destroy();
|
|
2424
|
+
}
|
|
2425
|
+
|
|
2426
|
+
if (Object.keys(toolStats).length === 0) {
|
|
2427
|
+
// No tool usage data available
|
|
2428
|
+
toolChart = new Chart(ctx, {
|
|
2429
|
+
type: 'bar',
|
|
2430
|
+
data: {
|
|
2431
|
+
labels: ['No Data'],
|
|
2432
|
+
datasets: [{
|
|
2433
|
+
label: 'Tool Usage',
|
|
2434
|
+
data: [0],
|
|
2435
|
+
backgroundColor: 'rgba(109, 40, 217, 0.6)',
|
|
2436
|
+
borderColor: 'rgba(109, 40, 217, 1)',
|
|
2437
|
+
borderWidth: 1
|
|
2438
|
+
}]
|
|
2439
|
+
},
|
|
2440
|
+
options: {
|
|
2441
|
+
responsive: true,
|
|
2442
|
+
maintainAspectRatio: false,
|
|
2443
|
+
interaction: {
|
|
2444
|
+
intersect: false,
|
|
2445
|
+
mode: 'nearest'
|
|
2446
|
+
},
|
|
2447
|
+
plugins: {
|
|
2448
|
+
legend: {
|
|
2449
|
+
display: false
|
|
2450
|
+
},
|
|
2451
|
+
tooltip: {
|
|
2452
|
+
enabled: true,
|
|
2453
|
+
position: 'nearest',
|
|
2454
|
+
backgroundColor: 'rgba(0, 0, 0, 0.8)',
|
|
2455
|
+
titleColor: '#fff',
|
|
2456
|
+
bodyColor: '#fff',
|
|
2457
|
+
borderColor: 'rgba(255, 255, 255, 0.1)',
|
|
2458
|
+
borderWidth: 1
|
|
2459
|
+
}
|
|
2460
|
+
},
|
|
2461
|
+
scales: {
|
|
2462
|
+
y: {
|
|
2463
|
+
beginAtZero: true,
|
|
2464
|
+
ticks: {
|
|
2465
|
+
color: getComputedStyle(document.documentElement).getPropertyValue('--text-secondary').trim()
|
|
2466
|
+
},
|
|
2467
|
+
grid: {
|
|
2468
|
+
color: getComputedStyle(document.documentElement).getPropertyValue('--border-primary').trim()
|
|
2469
|
+
}
|
|
2470
|
+
},
|
|
2471
|
+
x: {
|
|
2472
|
+
ticks: {
|
|
2473
|
+
color: getComputedStyle(document.documentElement).getPropertyValue('--text-secondary').trim()
|
|
2474
|
+
},
|
|
2475
|
+
grid: {
|
|
2476
|
+
color: getComputedStyle(document.documentElement).getPropertyValue('--border-primary').trim()
|
|
2477
|
+
}
|
|
2478
|
+
}
|
|
2479
|
+
}
|
|
2480
|
+
}
|
|
2481
|
+
});
|
|
2482
|
+
return;
|
|
2483
|
+
}
|
|
2484
|
+
|
|
2485
|
+
// Get top 10 most used tools
|
|
2486
|
+
const sortedTools = Object.entries(toolStats)
|
|
2487
|
+
.sort((a, b) => b[1] - a[1])
|
|
2488
|
+
.slice(0, 10);
|
|
2489
|
+
|
|
2490
|
+
// Generate colors for different tools
|
|
2491
|
+
const colors = [
|
|
2492
|
+
'rgba(75, 192, 192, 0.6)',
|
|
2493
|
+
'rgba(255, 99, 132, 0.6)',
|
|
2494
|
+
'rgba(54, 162, 235, 0.6)',
|
|
2495
|
+
'rgba(255, 206, 86, 0.6)',
|
|
2496
|
+
'rgba(153, 102, 255, 0.6)',
|
|
2497
|
+
'rgba(255, 159, 64, 0.6)',
|
|
2498
|
+
'rgba(199, 199, 199, 0.6)',
|
|
2499
|
+
'rgba(83, 102, 255, 0.6)',
|
|
2500
|
+
'rgba(255, 99, 255, 0.6)',
|
|
2501
|
+
'rgba(99, 255, 132, 0.6)'
|
|
2502
|
+
];
|
|
2503
|
+
|
|
2504
|
+
const borderColors = [
|
|
2505
|
+
'rgba(75, 192, 192, 1)',
|
|
2506
|
+
'rgba(255, 99, 132, 1)',
|
|
2507
|
+
'rgba(54, 162, 235, 1)',
|
|
2508
|
+
'rgba(255, 206, 86, 1)',
|
|
2509
|
+
'rgba(153, 102, 255, 1)',
|
|
2510
|
+
'rgba(255, 159, 64, 1)',
|
|
2511
|
+
'rgba(199, 199, 199, 1)',
|
|
2512
|
+
'rgba(83, 102, 255, 1)',
|
|
2513
|
+
'rgba(255, 99, 255, 1)',
|
|
2514
|
+
'rgba(99, 255, 132, 1)'
|
|
2515
|
+
];
|
|
2516
|
+
|
|
2517
|
+
// Store sorted tools data for reliable tooltip access
|
|
2518
|
+
window.currentToolData = sortedTools;
|
|
2519
|
+
|
|
2520
|
+
toolChart = new Chart(ctx, {
|
|
2521
|
+
type: 'bar',
|
|
2522
|
+
data: {
|
|
2523
|
+
labels: sortedTools.map(([tool]) => {
|
|
2524
|
+
// Truncate long tool names for display
|
|
2525
|
+
return tool.length > 15 ? tool.substring(0, 15) + '...' : tool;
|
|
2526
|
+
}),
|
|
2527
|
+
datasets: [{
|
|
2528
|
+
label: 'Usage Count',
|
|
2529
|
+
data: sortedTools.map(([, count]) => count),
|
|
2530
|
+
backgroundColor: colors.slice(0, sortedTools.length),
|
|
2531
|
+
borderColor: borderColors.slice(0, sortedTools.length),
|
|
2532
|
+
borderWidth: 1
|
|
2533
|
+
}]
|
|
2534
|
+
},
|
|
2535
|
+
options: {
|
|
2536
|
+
responsive: true,
|
|
2537
|
+
maintainAspectRatio: false,
|
|
2538
|
+
interaction: {
|
|
2539
|
+
intersect: false,
|
|
2540
|
+
mode: 'nearest'
|
|
2541
|
+
},
|
|
2542
|
+
plugins: {
|
|
2543
|
+
legend: {
|
|
2544
|
+
display: false
|
|
2545
|
+
},
|
|
2546
|
+
tooltip: {
|
|
2547
|
+
enabled: true,
|
|
2548
|
+
position: 'nearest',
|
|
2549
|
+
backgroundColor: 'rgba(0, 0, 0, 0.8)',
|
|
2550
|
+
titleColor: '#fff',
|
|
2551
|
+
bodyColor: '#fff',
|
|
2552
|
+
borderColor: 'rgba(255, 255, 255, 0.1)',
|
|
2553
|
+
borderWidth: 1,
|
|
2554
|
+
callbacks: {
|
|
2555
|
+
title: function(tooltipItems) {
|
|
2556
|
+
const index = tooltipItems[0].dataIndex;
|
|
2557
|
+
// Use the stored tool data for reliable access
|
|
2558
|
+
return window.currentToolData && window.currentToolData[index]
|
|
2559
|
+
? window.currentToolData[index][0]
|
|
2560
|
+
: 'Tool';
|
|
2561
|
+
},
|
|
2562
|
+
label: function(context) {
|
|
2563
|
+
const value = context.parsed.y;
|
|
2564
|
+
const total = window.currentToolData ?
|
|
2565
|
+
window.currentToolData.reduce((sum, [, count]) => sum + count, 0) :
|
|
2566
|
+
context.dataset.data.reduce((sum, val) => sum + val, 0);
|
|
2567
|
+
const percentage = ((value / total) * 100).toFixed(1);
|
|
2568
|
+
return `Used ${value} times (${percentage}%)`;
|
|
2569
|
+
}
|
|
2570
|
+
}
|
|
2571
|
+
}
|
|
2572
|
+
},
|
|
2573
|
+
scales: {
|
|
2574
|
+
y: {
|
|
2575
|
+
beginAtZero: true,
|
|
2576
|
+
ticks: {
|
|
2577
|
+
color: getComputedStyle(document.documentElement).getPropertyValue('--text-secondary').trim(),
|
|
2578
|
+
stepSize: 1
|
|
2579
|
+
},
|
|
2580
|
+
grid: {
|
|
2581
|
+
color: getComputedStyle(document.documentElement).getPropertyValue('--border-primary').trim()
|
|
2582
|
+
}
|
|
2583
|
+
},
|
|
2584
|
+
x: {
|
|
2585
|
+
ticks: {
|
|
2586
|
+
color: getComputedStyle(document.documentElement).getPropertyValue('--text-secondary').trim(),
|
|
2587
|
+
maxRotation: 45,
|
|
2588
|
+
minRotation: 0
|
|
2589
|
+
},
|
|
2590
|
+
grid: {
|
|
2591
|
+
color: getComputedStyle(document.documentElement).getPropertyValue('--border-primary').trim()
|
|
2592
|
+
}
|
|
2593
|
+
}
|
|
2594
|
+
}
|
|
2595
|
+
}
|
|
2596
|
+
});
|
|
2597
|
+
}
|
|
2598
|
+
|
|
2599
|
+
function updateToolSummary(conversations) {
|
|
2600
|
+
// Apply date filtering to conversations first
|
|
2601
|
+
const { fromDate, toDate } = getDateRange();
|
|
2602
|
+
const filteredConversations = conversations.filter(conv => {
|
|
2603
|
+
const convDate = new Date(conv.lastModified);
|
|
2604
|
+
return convDate >= fromDate && convDate <= toDate;
|
|
2605
|
+
});
|
|
2606
|
+
|
|
2607
|
+
// Aggregate tool usage across filtered conversations
|
|
2608
|
+
const toolStats = {};
|
|
2609
|
+
let totalToolCalls = 0;
|
|
2610
|
+
let conversationsWithTools = 0;
|
|
2611
|
+
let totalUniqueTools = new Set();
|
|
2612
|
+
|
|
2613
|
+
filteredConversations.forEach(conv => {
|
|
2614
|
+
if (conv.toolUsage && conv.toolUsage.toolTimeline) {
|
|
2615
|
+
let convHasTools = false;
|
|
2616
|
+
conv.toolUsage.toolTimeline.forEach(entry => {
|
|
2617
|
+
const entryDate = new Date(entry.timestamp);
|
|
2618
|
+
// Only include tool usage within date range
|
|
2619
|
+
if (entryDate >= fromDate && entryDate <= toDate) {
|
|
2620
|
+
toolStats[entry.tool] = (toolStats[entry.tool] || 0) + 1;
|
|
2621
|
+
totalToolCalls++;
|
|
2622
|
+
totalUniqueTools.add(entry.tool);
|
|
2623
|
+
convHasTools = true;
|
|
2624
|
+
}
|
|
2625
|
+
});
|
|
2626
|
+
if (convHasTools) {
|
|
2627
|
+
conversationsWithTools++;
|
|
2628
|
+
}
|
|
2629
|
+
}
|
|
2630
|
+
});
|
|
2631
|
+
|
|
2632
|
+
const summaryElement = document.getElementById('toolSummary');
|
|
2633
|
+
|
|
2634
|
+
if (totalToolCalls === 0) {
|
|
2635
|
+
summaryElement.innerHTML = `
|
|
2636
|
+
<div class="tool-stat">
|
|
2637
|
+
<span class="tool-stat-label">No tool usage data available</span>
|
|
2638
|
+
<span class="tool-stat-value">📊</span>
|
|
2639
|
+
</div>
|
|
2640
|
+
`;
|
|
2641
|
+
return;
|
|
2642
|
+
}
|
|
2643
|
+
|
|
2644
|
+
// Find most used tool
|
|
2645
|
+
const sortedTools = Object.entries(toolStats).sort((a, b) => b[1] - a[1]);
|
|
2646
|
+
const topTool = sortedTools[0];
|
|
2647
|
+
const avgToolsPerConv = conversationsWithTools > 0 ? (totalToolCalls / conversationsWithTools).toFixed(1) : 0;
|
|
2648
|
+
|
|
2649
|
+
// Calculate tool diversity using Shannon Diversity Index (more accurate)
|
|
2650
|
+
const toolCount = sortedTools.length;
|
|
2651
|
+
let shannonIndex = 0;
|
|
2652
|
+
let diversityScore = 0;
|
|
2653
|
+
|
|
2654
|
+
if (toolCount > 1) {
|
|
2655
|
+
// Calculate Shannon diversity index
|
|
2656
|
+
sortedTools.forEach(([, count]) => {
|
|
2657
|
+
const proportion = count / totalToolCalls;
|
|
2658
|
+
if (proportion > 0) {
|
|
2659
|
+
shannonIndex -= proportion * Math.log2(proportion);
|
|
2660
|
+
}
|
|
2661
|
+
});
|
|
2662
|
+
|
|
2663
|
+
// Normalize to 0-100% scale
|
|
2664
|
+
const maxDiversity = Math.log2(toolCount); // Maximum possible diversity
|
|
2665
|
+
diversityScore = (shannonIndex / maxDiversity) * 100;
|
|
2666
|
+
}
|
|
2667
|
+
// If only one tool used, diversityScore remains 0
|
|
2668
|
+
|
|
2669
|
+
summaryElement.innerHTML = `
|
|
2670
|
+
<div class="tool-stat">
|
|
2671
|
+
<span class="tool-stat-label">Total Tool Calls</span>
|
|
2672
|
+
<span class="tool-stat-value tool-stat-accent">${totalToolCalls}</span>
|
|
2673
|
+
</div>
|
|
2674
|
+
|
|
2675
|
+
<div class="tool-stat">
|
|
2676
|
+
<span class="tool-stat-label">Unique Tools Used</span>
|
|
2677
|
+
<span class="tool-stat-value">${totalUniqueTools.size}</span>
|
|
2678
|
+
</div>
|
|
2679
|
+
|
|
2680
|
+
<div class="tool-stat">
|
|
2681
|
+
<span class="tool-stat-label">Conversations with Tools</span>
|
|
2682
|
+
<span class="tool-stat-value">${conversationsWithTools}/${filteredConversations.length}</span>
|
|
2683
|
+
</div>
|
|
2684
|
+
|
|
2685
|
+
<div class="tool-stat">
|
|
2686
|
+
<span class="tool-stat-label">Avg Tools per Conversation</span>
|
|
2687
|
+
<span class="tool-stat-value">${avgToolsPerConv}</span>
|
|
2688
|
+
</div>
|
|
2689
|
+
|
|
2690
|
+
<div class="tool-stat" title="How evenly distributed tool usage is. 100% = perfectly balanced, 0% = concentrated in few tools">
|
|
2691
|
+
<span class="tool-stat-label">Tool Balance Score</span>
|
|
2692
|
+
<span class="tool-stat-value">${diversityScore.toFixed(0)}%</span>
|
|
2693
|
+
</div>
|
|
2694
|
+
|
|
2695
|
+
${topTool ? `
|
|
2696
|
+
<div class="tool-top-tool">
|
|
2697
|
+
<div class="tool-icon">🏆</div>
|
|
2698
|
+
<div class="tool-info">
|
|
2699
|
+
<div class="tool-name">Most Used: ${topTool[0]}</div>
|
|
2700
|
+
<div class="tool-usage">${topTool[1]} calls (${((topTool[1] / totalToolCalls) * 100).toFixed(1)}%)</div>
|
|
2701
|
+
</div>
|
|
2702
|
+
</div>
|
|
2703
|
+
` : ''}
|
|
2704
|
+
`;
|
|
2705
|
+
}
|
|
2706
|
+
|
|
2150
2707
|
function updateSessionsTable() {
|
|
2151
2708
|
const tableBody = document.getElementById('sessionsTable');
|
|
2152
2709
|
const noSessionsDiv = document.getElementById('noSessions');
|
|
@@ -2646,6 +3203,9 @@
|
|
|
2646
3203
|
document.getElementById('dateFrom').addEventListener('change', refreshCharts);
|
|
2647
3204
|
document.getElementById('dateTo').addEventListener('change', refreshCharts);
|
|
2648
3205
|
|
|
3206
|
+
// Initialize theme toggle
|
|
3207
|
+
initializeTheme();
|
|
3208
|
+
|
|
2649
3209
|
// Initialize notification button state
|
|
2650
3210
|
updateNotificationButtonState();
|
|
2651
3211
|
});
|
|
@@ -2668,7 +3228,11 @@
|
|
|
2668
3228
|
getSessionData: async () => {
|
|
2669
3229
|
try {
|
|
2670
3230
|
const response = await fetch('/api/session/data');
|
|
2671
|
-
|
|
3231
|
+
if (!response.ok) {
|
|
3232
|
+
throw new Error(`HTTP error! status: ${response.status}`);
|
|
3233
|
+
}
|
|
3234
|
+
const data = await response.json();
|
|
3235
|
+
return data;
|
|
2672
3236
|
} catch (error) {
|
|
2673
3237
|
console.error('Error fetching session data:', error);
|
|
2674
3238
|
return null;
|
|
@@ -2707,6 +3271,49 @@
|
|
|
2707
3271
|
}
|
|
2708
3272
|
}
|
|
2709
3273
|
|
|
3274
|
+
// Theme toggle functionality
|
|
3275
|
+
function initializeTheme() {
|
|
3276
|
+
const savedTheme = localStorage.getItem('claude-analytics-theme') || 'dark';
|
|
3277
|
+
const body = document.body;
|
|
3278
|
+
const themeSwitch = document.getElementById('themeSwitch');
|
|
3279
|
+
const themeSwitchThumb = document.getElementById('themeSwitchThumb');
|
|
3280
|
+
const themeIcon = themeSwitchThumb.querySelector('.theme-switch-icon');
|
|
3281
|
+
|
|
3282
|
+
function setTheme(theme) {
|
|
3283
|
+
if (theme === 'light') {
|
|
3284
|
+
body.setAttribute('data-theme', 'light');
|
|
3285
|
+
themeSwitchThumb.classList.add('light');
|
|
3286
|
+
themeIcon.textContent = '☀️';
|
|
3287
|
+
} else {
|
|
3288
|
+
body.removeAttribute('data-theme');
|
|
3289
|
+
themeSwitchThumb.classList.remove('light');
|
|
3290
|
+
themeIcon.textContent = '🌙';
|
|
3291
|
+
}
|
|
3292
|
+
localStorage.setItem('claude-analytics-theme', theme);
|
|
3293
|
+
|
|
3294
|
+
// Update charts if they exist to use new theme colors
|
|
3295
|
+
if (tokenChart || projectChart || toolChart) {
|
|
3296
|
+
setTimeout(() => {
|
|
3297
|
+
if (allData) {
|
|
3298
|
+
updateCharts(allData);
|
|
3299
|
+
}
|
|
3300
|
+
}, 100); // Small delay to let CSS variables update
|
|
3301
|
+
}
|
|
3302
|
+
}
|
|
3303
|
+
|
|
3304
|
+
function toggleTheme() {
|
|
3305
|
+
const currentTheme = body.getAttribute('data-theme') === 'light' ? 'light' : 'dark';
|
|
3306
|
+
const newTheme = currentTheme === 'dark' ? 'light' : 'dark';
|
|
3307
|
+
setTheme(newTheme);
|
|
3308
|
+
}
|
|
3309
|
+
|
|
3310
|
+
// Set initial theme
|
|
3311
|
+
setTheme(savedTheme);
|
|
3312
|
+
|
|
3313
|
+
// Add click event listener
|
|
3314
|
+
themeSwitch.addEventListener('click', toggleTheme);
|
|
3315
|
+
}
|
|
3316
|
+
|
|
2710
3317
|
// Add keyboard shortcut for refresh (F5 or Ctrl+R)
|
|
2711
3318
|
document.addEventListener('keydown', function(e) {
|
|
2712
3319
|
if (e.key === 'F5' || (e.ctrlKey && e.key === 'r')) {
|