claude-code-templates 1.8.1 → 1.8.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 +2 -2
- package/src/analytics/core/ConversationAnalyzer.js +1 -1
- package/src/analytics/core/FileWatcher.js +4 -4
- package/src/analytics/core/SessionAnalyzer.js +44 -10
- package/src/analytics/data/DataCache.js +8 -8
- package/src/analytics-web/index.html +213 -31
- package/src/analytics.js +21 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-code-templates",
|
|
3
|
-
"version": "1.8.
|
|
3
|
+
"version": "1.8.3",
|
|
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,7 @@
|
|
|
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": "
|
|
34
|
+
"prepublishOnly-disabled": "npm run test:coverage",
|
|
35
35
|
"analytics:start": "node src/analytics.js",
|
|
36
36
|
"analytics:test": "npm run test:analytics"
|
|
37
37
|
},
|
|
@@ -125,7 +125,7 @@ class ConversationAnalyzer {
|
|
|
125
125
|
status: stateCalculator.determineConversationStatus(parsedMessages, stats.mtime),
|
|
126
126
|
conversationState: stateCalculator.determineConversationState(parsedMessages, stats.mtime),
|
|
127
127
|
statusSquares: await this.getCachedStatusSquares(filePath, parsedMessages),
|
|
128
|
-
|
|
128
|
+
// parsedMessages removed to prevent memory leak - available via cache when needed
|
|
129
129
|
};
|
|
130
130
|
|
|
131
131
|
conversations.push(conversation);
|
|
@@ -96,20 +96,20 @@ class FileWatcher {
|
|
|
96
96
|
* Setup periodic refresh intervals
|
|
97
97
|
*/
|
|
98
98
|
setupPeriodicRefresh() {
|
|
99
|
-
// Periodic refresh to catch any missed changes
|
|
99
|
+
// Periodic refresh to catch any missed changes (reduced frequency)
|
|
100
100
|
const dataRefreshInterval = setInterval(async () => {
|
|
101
101
|
console.log(chalk.blue('⏱️ Periodic data refresh...'));
|
|
102
102
|
await this.triggerDataRefresh();
|
|
103
|
-
},
|
|
103
|
+
}, 120000); // Every 2 minutes (reduced from 30 seconds)
|
|
104
104
|
|
|
105
105
|
this.intervals.push(dataRefreshInterval);
|
|
106
106
|
|
|
107
|
-
//
|
|
107
|
+
// Process updates for active processes (reduced frequency)
|
|
108
108
|
const processRefreshInterval = setInterval(async () => {
|
|
109
109
|
if (this.processRefreshCallback) {
|
|
110
110
|
await this.processRefreshCallback();
|
|
111
111
|
}
|
|
112
|
-
},
|
|
112
|
+
}, 30000); // Every 30 seconds (reduced from 10 seconds)
|
|
113
113
|
|
|
114
114
|
this.intervals.push(processRefreshInterval);
|
|
115
115
|
}
|
|
@@ -127,6 +127,38 @@ class SessionAnalyzer {
|
|
|
127
127
|
};
|
|
128
128
|
}
|
|
129
129
|
|
|
130
|
+
/**
|
|
131
|
+
* Generate estimated messages for session analysis when parsedMessages is not available
|
|
132
|
+
* @param {Object} conversation - Conversation object
|
|
133
|
+
* @returns {Array} Array of estimated message objects
|
|
134
|
+
*/
|
|
135
|
+
generateEstimatedMessages(conversation) {
|
|
136
|
+
const messages = [];
|
|
137
|
+
const messageCount = conversation.messageCount || 0;
|
|
138
|
+
const created = new Date(conversation.created);
|
|
139
|
+
const lastModified = new Date(conversation.lastModified);
|
|
140
|
+
|
|
141
|
+
if (messageCount === 0) return messages;
|
|
142
|
+
|
|
143
|
+
// Estimate message distribution over time
|
|
144
|
+
const timeDiff = lastModified - created;
|
|
145
|
+
const timePerMessage = timeDiff / messageCount;
|
|
146
|
+
|
|
147
|
+
// Generate alternating user/assistant messages
|
|
148
|
+
for (let i = 0; i < messageCount; i++) {
|
|
149
|
+
const timestamp = new Date(created.getTime() + (i * timePerMessage));
|
|
150
|
+
const role = i % 2 === 0 ? 'user' : 'assistant';
|
|
151
|
+
|
|
152
|
+
messages.push({
|
|
153
|
+
timestamp: timestamp,
|
|
154
|
+
role: role,
|
|
155
|
+
usage: conversation.tokenUsage || null
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
return messages;
|
|
160
|
+
}
|
|
161
|
+
|
|
130
162
|
/**
|
|
131
163
|
* Extract 5-hour sliding window sessions from conversations
|
|
132
164
|
* @param {Array} conversations - Array of conversation objects
|
|
@@ -137,15 +169,16 @@ class SessionAnalyzer {
|
|
|
137
169
|
const allMessages = [];
|
|
138
170
|
|
|
139
171
|
conversations.forEach(conversation => {
|
|
140
|
-
|
|
172
|
+
// Skip conversations without message count or with zero messages
|
|
173
|
+
if (!conversation.messageCount || conversation.messageCount === 0) {
|
|
141
174
|
return;
|
|
142
175
|
}
|
|
143
176
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
);
|
|
177
|
+
// Generate estimated messages based on token usage and timestamps
|
|
178
|
+
// This is a fallback when parsedMessages is not available
|
|
179
|
+
const estimatedMessages = this.generateEstimatedMessages(conversation);
|
|
147
180
|
|
|
148
|
-
|
|
181
|
+
estimatedMessages.forEach(message => {
|
|
149
182
|
allMessages.push({
|
|
150
183
|
timestamp: message.timestamp,
|
|
151
184
|
role: message.role,
|
|
@@ -268,15 +301,16 @@ class SessionAnalyzer {
|
|
|
268
301
|
const allMessages = [];
|
|
269
302
|
|
|
270
303
|
conversations.forEach(conversation => {
|
|
271
|
-
|
|
304
|
+
// Skip conversations without message count or with zero messages
|
|
305
|
+
if (!conversation.messageCount || conversation.messageCount === 0) {
|
|
272
306
|
return;
|
|
273
307
|
}
|
|
274
308
|
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
);
|
|
309
|
+
// Generate estimated messages based on token usage and timestamps
|
|
310
|
+
// This is a fallback when parsedMessages is not available
|
|
311
|
+
const estimatedMessages = this.generateEstimatedMessages(conversation);
|
|
278
312
|
|
|
279
|
-
|
|
313
|
+
estimatedMessages.forEach(message => {
|
|
280
314
|
allMessages.push({
|
|
281
315
|
timestamp: message.timestamp,
|
|
282
316
|
role: message.role,
|
|
@@ -32,14 +32,14 @@ class DataCache {
|
|
|
32
32
|
projectStats: new Map(), // projectPath -> { data, timestamp }
|
|
33
33
|
};
|
|
34
34
|
|
|
35
|
-
// Cache configuration
|
|
35
|
+
// Cache configuration (reduced TTL for aggressive memory management)
|
|
36
36
|
this.config = {
|
|
37
|
-
fileContentTTL:
|
|
38
|
-
parsedDataTTL:
|
|
39
|
-
computationTTL:
|
|
40
|
-
metadataTTL:
|
|
37
|
+
fileContentTTL: 30000, // 30 seconds for file content (reduced from 60s)
|
|
38
|
+
parsedDataTTL: 15000, // 15 seconds for parsed data (reduced from 30s)
|
|
39
|
+
computationTTL: 10000, // 10 seconds for expensive computations (reduced from 20s)
|
|
40
|
+
metadataTTL: 5000, // 5 seconds for metadata (reduced from 10s)
|
|
41
41
|
processTTL: 500, // 500ms for process data
|
|
42
|
-
maxCacheSize:
|
|
42
|
+
maxCacheSize: 25, // Aggressively reduced to 25 to prevent memory buildup
|
|
43
43
|
};
|
|
44
44
|
|
|
45
45
|
// Dependency tracking for smart invalidation
|
|
@@ -55,11 +55,11 @@ class DataCache {
|
|
|
55
55
|
evictions: 0
|
|
56
56
|
};
|
|
57
57
|
|
|
58
|
-
// Start automatic cleanup interval
|
|
58
|
+
// Start automatic cleanup interval (more aggressive)
|
|
59
59
|
this.cleanupInterval = setInterval(() => {
|
|
60
60
|
this.evictOldEntries();
|
|
61
61
|
this.enforceSizeLimits();
|
|
62
|
-
},
|
|
62
|
+
}, 15000); // Every 15 seconds (reduced from 30 seconds)
|
|
63
63
|
}
|
|
64
64
|
|
|
65
65
|
/**
|
|
@@ -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
|
|
|
@@ -1317,12 +1430,20 @@
|
|
|
1317
1430
|
padding: 6px 8px;
|
|
1318
1431
|
}
|
|
1319
1432
|
|
|
1320
|
-
.
|
|
1433
|
+
.header-actions {
|
|
1321
1434
|
position: relative;
|
|
1322
1435
|
margin-top: 12px;
|
|
1323
1436
|
align-self: flex-start;
|
|
1437
|
+
gap: 12px;
|
|
1438
|
+
}
|
|
1439
|
+
|
|
1440
|
+
.github-star-btn {
|
|
1441
|
+
position: relative;
|
|
1442
|
+
margin-top: 0;
|
|
1443
|
+
align-self: flex-start;
|
|
1324
1444
|
}
|
|
1325
1445
|
|
|
1446
|
+
|
|
1326
1447
|
.terminal-header {
|
|
1327
1448
|
display: flex;
|
|
1328
1449
|
flex-direction: column;
|
|
@@ -1340,10 +1461,21 @@
|
|
|
1340
1461
|
<div class="terminal-subtitle">real-time monitoring dashboard</div>
|
|
1341
1462
|
<div class="terminal-subtitle" id="lastUpdate"></div>
|
|
1342
1463
|
|
|
1343
|
-
<
|
|
1344
|
-
<
|
|
1345
|
-
|
|
1346
|
-
|
|
1464
|
+
<div class="header-actions">
|
|
1465
|
+
<div class="theme-switch-container" title="Toggle light/dark theme">
|
|
1466
|
+
<div class="theme-switch" id="themeSwitch">
|
|
1467
|
+
<div class="theme-switch-track">
|
|
1468
|
+
<div class="theme-switch-thumb" id="themeSwitchThumb">
|
|
1469
|
+
<span class="theme-switch-icon">🌙</span>
|
|
1470
|
+
</div>
|
|
1471
|
+
</div>
|
|
1472
|
+
</div>
|
|
1473
|
+
</div>
|
|
1474
|
+
<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!">
|
|
1475
|
+
<span class="star-icon">⭐</span>
|
|
1476
|
+
<span>Star on GitHub</span>
|
|
1477
|
+
</a>
|
|
1478
|
+
</div>
|
|
1347
1479
|
</div>
|
|
1348
1480
|
|
|
1349
1481
|
<div id="loading" class="loading">
|
|
@@ -1962,11 +2094,11 @@
|
|
|
1962
2094
|
datasets: [{
|
|
1963
2095
|
label: 'Tokens',
|
|
1964
2096
|
data: dateRange.map(day => day.tokens),
|
|
1965
|
-
borderColor: '
|
|
1966
|
-
backgroundColor: '
|
|
2097
|
+
borderColor: getComputedStyle(document.documentElement).getPropertyValue('--text-accent').trim(),
|
|
2098
|
+
backgroundColor: getComputedStyle(document.documentElement).getPropertyValue('--text-accent').trim() + '20',
|
|
1967
2099
|
borderWidth: 2,
|
|
1968
|
-
pointBackgroundColor: '
|
|
1969
|
-
pointBorderColor: '
|
|
2100
|
+
pointBackgroundColor: getComputedStyle(document.documentElement).getPropertyValue('--text-accent').trim(),
|
|
2101
|
+
pointBorderColor: getComputedStyle(document.documentElement).getPropertyValue('--text-accent').trim(),
|
|
1970
2102
|
pointRadius: 4,
|
|
1971
2103
|
pointHoverRadius: 6,
|
|
1972
2104
|
fill: true,
|
|
@@ -2076,14 +2208,14 @@
|
|
|
2076
2208
|
sortedProjects.push(['others', othersTotal]);
|
|
2077
2209
|
}
|
|
2078
2210
|
|
|
2079
|
-
// Terminal-style colors
|
|
2211
|
+
// Terminal-style colors using CSS variables
|
|
2080
2212
|
const colors = [
|
|
2081
|
-
'
|
|
2082
|
-
'
|
|
2083
|
-
'
|
|
2084
|
-
'
|
|
2085
|
-
'
|
|
2086
|
-
'
|
|
2213
|
+
getComputedStyle(document.documentElement).getPropertyValue('--text-accent').trim(),
|
|
2214
|
+
getComputedStyle(document.documentElement).getPropertyValue('--text-success').trim(),
|
|
2215
|
+
getComputedStyle(document.documentElement).getPropertyValue('--text-info').trim(),
|
|
2216
|
+
getComputedStyle(document.documentElement).getPropertyValue('--text-warning').trim(),
|
|
2217
|
+
getComputedStyle(document.documentElement).getPropertyValue('--text-primary').trim(),
|
|
2218
|
+
getComputedStyle(document.documentElement).getPropertyValue('--text-secondary').trim()
|
|
2087
2219
|
];
|
|
2088
2220
|
|
|
2089
2221
|
const ctx = document.getElementById('projectChart').getContext('2d');
|
|
@@ -2099,7 +2231,7 @@
|
|
|
2099
2231
|
datasets: [{
|
|
2100
2232
|
data: sortedProjects.map(([,tokens]) => tokens),
|
|
2101
2233
|
backgroundColor: colors.slice(0, sortedProjects.length),
|
|
2102
|
-
borderColor: '
|
|
2234
|
+
borderColor: getComputedStyle(document.documentElement).getPropertyValue('--bg-secondary').trim(),
|
|
2103
2235
|
borderWidth: 2,
|
|
2104
2236
|
hoverBorderWidth: 3
|
|
2105
2237
|
}]
|
|
@@ -2646,6 +2778,9 @@
|
|
|
2646
2778
|
document.getElementById('dateFrom').addEventListener('change', refreshCharts);
|
|
2647
2779
|
document.getElementById('dateTo').addEventListener('change', refreshCharts);
|
|
2648
2780
|
|
|
2781
|
+
// Initialize theme toggle
|
|
2782
|
+
initializeTheme();
|
|
2783
|
+
|
|
2649
2784
|
// Initialize notification button state
|
|
2650
2785
|
updateNotificationButtonState();
|
|
2651
2786
|
});
|
|
@@ -2668,7 +2803,11 @@
|
|
|
2668
2803
|
getSessionData: async () => {
|
|
2669
2804
|
try {
|
|
2670
2805
|
const response = await fetch('/api/session/data');
|
|
2671
|
-
|
|
2806
|
+
if (!response.ok) {
|
|
2807
|
+
throw new Error(`HTTP error! status: ${response.status}`);
|
|
2808
|
+
}
|
|
2809
|
+
const data = await response.json();
|
|
2810
|
+
return data;
|
|
2672
2811
|
} catch (error) {
|
|
2673
2812
|
console.error('Error fetching session data:', error);
|
|
2674
2813
|
return null;
|
|
@@ -2707,6 +2846,49 @@
|
|
|
2707
2846
|
}
|
|
2708
2847
|
}
|
|
2709
2848
|
|
|
2849
|
+
// Theme toggle functionality
|
|
2850
|
+
function initializeTheme() {
|
|
2851
|
+
const savedTheme = localStorage.getItem('claude-analytics-theme') || 'dark';
|
|
2852
|
+
const body = document.body;
|
|
2853
|
+
const themeSwitch = document.getElementById('themeSwitch');
|
|
2854
|
+
const themeSwitchThumb = document.getElementById('themeSwitchThumb');
|
|
2855
|
+
const themeIcon = themeSwitchThumb.querySelector('.theme-switch-icon');
|
|
2856
|
+
|
|
2857
|
+
function setTheme(theme) {
|
|
2858
|
+
if (theme === 'light') {
|
|
2859
|
+
body.setAttribute('data-theme', 'light');
|
|
2860
|
+
themeSwitchThumb.classList.add('light');
|
|
2861
|
+
themeIcon.textContent = '☀️';
|
|
2862
|
+
} else {
|
|
2863
|
+
body.removeAttribute('data-theme');
|
|
2864
|
+
themeSwitchThumb.classList.remove('light');
|
|
2865
|
+
themeIcon.textContent = '🌙';
|
|
2866
|
+
}
|
|
2867
|
+
localStorage.setItem('claude-analytics-theme', theme);
|
|
2868
|
+
|
|
2869
|
+
// Update charts if they exist to use new theme colors
|
|
2870
|
+
if (tokenChart || projectChart) {
|
|
2871
|
+
setTimeout(() => {
|
|
2872
|
+
if (allData) {
|
|
2873
|
+
updateCharts(allData);
|
|
2874
|
+
}
|
|
2875
|
+
}, 100); // Small delay to let CSS variables update
|
|
2876
|
+
}
|
|
2877
|
+
}
|
|
2878
|
+
|
|
2879
|
+
function toggleTheme() {
|
|
2880
|
+
const currentTheme = body.getAttribute('data-theme') === 'light' ? 'light' : 'dark';
|
|
2881
|
+
const newTheme = currentTheme === 'dark' ? 'light' : 'dark';
|
|
2882
|
+
setTheme(newTheme);
|
|
2883
|
+
}
|
|
2884
|
+
|
|
2885
|
+
// Set initial theme
|
|
2886
|
+
setTheme(savedTheme);
|
|
2887
|
+
|
|
2888
|
+
// Add click event listener
|
|
2889
|
+
themeSwitch.addEventListener('click', toggleTheme);
|
|
2890
|
+
}
|
|
2891
|
+
|
|
2710
2892
|
// Add keyboard shortcut for refresh (F5 or Ctrl+R)
|
|
2711
2893
|
document.addEventListener('keydown', function(e) {
|
|
2712
2894
|
if (e.key === 'F5' || (e.ctrlKey && e.key === 'r')) {
|
package/src/analytics.js
CHANGED
|
@@ -530,6 +530,14 @@ class ClaudeAnalytics {
|
|
|
530
530
|
// Calculate detailed token usage
|
|
531
531
|
const detailedTokenUsage = this.calculateDetailedTokenUsage();
|
|
532
532
|
|
|
533
|
+
// Memory cleanup: limit conversation history to prevent memory buildup
|
|
534
|
+
if (this.data.conversations && this.data.conversations.length > 150) {
|
|
535
|
+
console.log(chalk.yellow(`🧹 Cleaning up conversation history: ${this.data.conversations.length} -> 150`));
|
|
536
|
+
this.data.conversations = this.data.conversations
|
|
537
|
+
.sort((a, b) => new Date(b.lastModified) - new Date(a.lastModified))
|
|
538
|
+
.slice(0, 150);
|
|
539
|
+
}
|
|
540
|
+
|
|
533
541
|
// Add timestamp to verify data freshness
|
|
534
542
|
const dataWithTimestamp = {
|
|
535
543
|
...this.data,
|
|
@@ -798,6 +806,19 @@ class ClaudeAnalytics {
|
|
|
798
806
|
}
|
|
799
807
|
}
|
|
800
808
|
|
|
809
|
+
// Memory cleanup: limit conversation history to prevent memory buildup
|
|
810
|
+
if (this.data.conversations.length > 100) {
|
|
811
|
+
console.log(chalk.yellow(`🧹 Cleaning up conversation history: ${this.data.conversations.length} -> 100`));
|
|
812
|
+
this.data.conversations = this.data.conversations
|
|
813
|
+
.sort((a, b) => new Date(b.lastModified) - new Date(a.lastModified))
|
|
814
|
+
.slice(0, 100);
|
|
815
|
+
}
|
|
816
|
+
|
|
817
|
+
// Force garbage collection hint
|
|
818
|
+
if (global.gc) {
|
|
819
|
+
global.gc();
|
|
820
|
+
}
|
|
821
|
+
|
|
801
822
|
const dataWithTimestamp = {
|
|
802
823
|
conversations: this.data.conversations,
|
|
803
824
|
summary: this.data.summary,
|