claude-code-templates 1.20.2 → 1.20.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/analytics/core/AgentAnalyzer.js +3 -3
- package/src/analytics/core/ConversationAnalyzer.js +2 -1
- package/src/analytics-web/components/ActivityHeatmap.js +738 -0
- package/src/analytics-web/components/DashboardPage.js +54 -0
- package/src/analytics-web/index.html +471 -1
- package/src/analytics.js +187 -0
package/src/analytics.js
CHANGED
|
@@ -1191,6 +1191,27 @@ class ClaudeAnalytics {
|
|
|
1191
1191
|
}
|
|
1192
1192
|
});
|
|
1193
1193
|
|
|
1194
|
+
// Activity heatmap data endpoint - needs full conversation history
|
|
1195
|
+
this.app.get('/api/activity', async (req, res) => {
|
|
1196
|
+
try {
|
|
1197
|
+
console.log(`🔥 /api/activity called - loading all conversations...`);
|
|
1198
|
+
// Get all conversations without the 150 limit for accurate heatmap data
|
|
1199
|
+
const allConversations = await this.conversationAnalyzer.loadConversations(this.stateCalculator);
|
|
1200
|
+
console.log(`🔥 Loaded ${allConversations.length} conversations from server`);
|
|
1201
|
+
|
|
1202
|
+
// Generate activity data using complete dataset
|
|
1203
|
+
const activityData = this.generateActivityDataFromConversations(allConversations);
|
|
1204
|
+
res.json({
|
|
1205
|
+
conversations: allConversations, // Also include conversations for the heatmap component
|
|
1206
|
+
...activityData,
|
|
1207
|
+
timestamp: new Date().toISOString()
|
|
1208
|
+
});
|
|
1209
|
+
} catch (error) {
|
|
1210
|
+
console.error('Error generating activity data:', error);
|
|
1211
|
+
res.status(500).json({ error: 'Failed to generate activity data' });
|
|
1212
|
+
}
|
|
1213
|
+
});
|
|
1214
|
+
|
|
1194
1215
|
// Main dashboard route
|
|
1195
1216
|
this.app.get('/', (req, res) => {
|
|
1196
1217
|
res.sendFile(path.join(__dirname, 'analytics-web', 'index.html'));
|
|
@@ -2033,6 +2054,172 @@ class ClaudeAnalytics {
|
|
|
2033
2054
|
}
|
|
2034
2055
|
}
|
|
2035
2056
|
|
|
2057
|
+
/**
|
|
2058
|
+
* Generate activity data for the heatmap using complete conversation set
|
|
2059
|
+
* @param {Array} conversations - Complete conversation array (not limited)
|
|
2060
|
+
* @returns {Object} Activity data including daily contributions and stats
|
|
2061
|
+
*/
|
|
2062
|
+
generateActivityDataFromConversations(conversations) {
|
|
2063
|
+
const dailyActivity = new Map();
|
|
2064
|
+
const oneYearAgo = new Date();
|
|
2065
|
+
oneYearAgo.setFullYear(oneYearAgo.getFullYear() - 1);
|
|
2066
|
+
oneYearAgo.setHours(0, 0, 0, 0);
|
|
2067
|
+
|
|
2068
|
+
const today = new Date();
|
|
2069
|
+
today.setHours(23, 59, 59, 999);
|
|
2070
|
+
|
|
2071
|
+
console.log(`🔥 Generating activity data for ${conversations.length} conversations (FULL DATASET)...`);
|
|
2072
|
+
|
|
2073
|
+
// Show date range of conversations
|
|
2074
|
+
if (conversations.length > 0) {
|
|
2075
|
+
const dates = conversations.map(c => c.lastModified ? new Date(c.lastModified) : null).filter(Boolean);
|
|
2076
|
+
dates.sort((a, b) => a - b);
|
|
2077
|
+
console.log(`🔥 Server conversation date range: ${dates[0]?.toLocaleDateString()} to ${dates[dates.length - 1]?.toLocaleDateString()}`);
|
|
2078
|
+
|
|
2079
|
+
// Show some sample conversations with dates
|
|
2080
|
+
console.log(`🔥 First 3 conversations by date:`);
|
|
2081
|
+
const sortedConversations = conversations
|
|
2082
|
+
.filter(c => c.lastModified)
|
|
2083
|
+
.sort((a, b) => new Date(a.lastModified) - new Date(b.lastModified))
|
|
2084
|
+
.slice(0, 3);
|
|
2085
|
+
sortedConversations.forEach((conv, i) => {
|
|
2086
|
+
console.log(` ${i+1}: ${conv.filename} - ${new Date(conv.lastModified).toLocaleDateString()}`);
|
|
2087
|
+
});
|
|
2088
|
+
}
|
|
2089
|
+
|
|
2090
|
+
// Process conversations for daily activity
|
|
2091
|
+
conversations.forEach(conversation => {
|
|
2092
|
+
if (!conversation.lastModified) return;
|
|
2093
|
+
|
|
2094
|
+
const date = new Date(conversation.lastModified);
|
|
2095
|
+
if (date < oneYearAgo || date > today) return;
|
|
2096
|
+
|
|
2097
|
+
const dateKey = date.toISOString().split('T')[0]; // YYYY-MM-DD
|
|
2098
|
+
|
|
2099
|
+
const current = dailyActivity.get(dateKey) || {
|
|
2100
|
+
conversations: 0,
|
|
2101
|
+
tokens: 0,
|
|
2102
|
+
messages: 0,
|
|
2103
|
+
tools: 0,
|
|
2104
|
+
date: dateKey
|
|
2105
|
+
};
|
|
2106
|
+
|
|
2107
|
+
current.conversations += 1;
|
|
2108
|
+
current.tokens += conversation.tokens || 0;
|
|
2109
|
+
current.messages += conversation.messageCount || 0;
|
|
2110
|
+
current.tools += (conversation.toolUsage?.totalToolCalls || 0);
|
|
2111
|
+
|
|
2112
|
+
dailyActivity.set(dateKey, current);
|
|
2113
|
+
});
|
|
2114
|
+
|
|
2115
|
+
// Convert to array and sort by date
|
|
2116
|
+
const activityArray = Array.from(dailyActivity.values())
|
|
2117
|
+
.sort((a, b) => a.date.localeCompare(b.date));
|
|
2118
|
+
|
|
2119
|
+
// Calculate stats
|
|
2120
|
+
const totalContributions = activityArray.reduce((sum, day) => sum + day.conversations, 0);
|
|
2121
|
+
const totalTools = activityArray.reduce((sum, day) => sum + day.tools, 0);
|
|
2122
|
+
const { longestStreak, currentStreak } = this.calculateStreaks(activityArray);
|
|
2123
|
+
|
|
2124
|
+
console.log(`🔥 Activity data generated: ${activityArray.length} active days, ${totalContributions} total contributions, ${totalTools} tool calls`);
|
|
2125
|
+
|
|
2126
|
+
return {
|
|
2127
|
+
dailyActivity: activityArray,
|
|
2128
|
+
totalContributions,
|
|
2129
|
+
totalTools,
|
|
2130
|
+
longestStreak,
|
|
2131
|
+
currentStreak,
|
|
2132
|
+
activeDays: activityArray.length,
|
|
2133
|
+
startDate: oneYearAgo.toISOString().split('T')[0],
|
|
2134
|
+
endDate: today.toISOString().split('T')[0]
|
|
2135
|
+
};
|
|
2136
|
+
}
|
|
2137
|
+
|
|
2138
|
+
/**
|
|
2139
|
+
* Generate activity data for the heatmap (legacy method for backward compatibility)
|
|
2140
|
+
* @returns {Object} Activity data including daily contributions and stats
|
|
2141
|
+
*/
|
|
2142
|
+
generateActivityData() {
|
|
2143
|
+
if (!this.data || !this.data.conversations) {
|
|
2144
|
+
return {
|
|
2145
|
+
dailyActivity: [],
|
|
2146
|
+
totalContributions: 0,
|
|
2147
|
+
longestStreak: 0,
|
|
2148
|
+
currentStreak: 0
|
|
2149
|
+
};
|
|
2150
|
+
}
|
|
2151
|
+
|
|
2152
|
+
return this.generateActivityDataFromConversations(this.data.conversations);
|
|
2153
|
+
}
|
|
2154
|
+
|
|
2155
|
+
/**
|
|
2156
|
+
* Calculate contribution streaks
|
|
2157
|
+
* @param {Array} activityArray - Sorted array of daily activity
|
|
2158
|
+
* @returns {Object} Longest and current streak counts
|
|
2159
|
+
*/
|
|
2160
|
+
calculateStreaks(activityArray) {
|
|
2161
|
+
if (activityArray.length === 0) {
|
|
2162
|
+
return { longestStreak: 0, currentStreak: 0 };
|
|
2163
|
+
}
|
|
2164
|
+
|
|
2165
|
+
let longestStreak = 0;
|
|
2166
|
+
let currentStreak = 0;
|
|
2167
|
+
let tempStreak = 0;
|
|
2168
|
+
|
|
2169
|
+
const today = new Date();
|
|
2170
|
+
today.setHours(0, 0, 0, 0);
|
|
2171
|
+
|
|
2172
|
+
// Create a map of active dates for quick lookup
|
|
2173
|
+
const activeDates = new Set(activityArray.map(day => day.date));
|
|
2174
|
+
|
|
2175
|
+
// Check streak going backwards from today
|
|
2176
|
+
let checkDate = new Date(today);
|
|
2177
|
+
let foundToday = false;
|
|
2178
|
+
|
|
2179
|
+
// First check if today or yesterday has activity
|
|
2180
|
+
for (let i = 0; i < 2; i++) {
|
|
2181
|
+
const dateKey = checkDate.toISOString().split('T')[0];
|
|
2182
|
+
if (activeDates.has(dateKey)) {
|
|
2183
|
+
foundToday = true;
|
|
2184
|
+
break;
|
|
2185
|
+
}
|
|
2186
|
+
checkDate.setDate(checkDate.getDate() - 1);
|
|
2187
|
+
}
|
|
2188
|
+
|
|
2189
|
+
if (foundToday) {
|
|
2190
|
+
// Calculate current streak
|
|
2191
|
+
checkDate = new Date(today);
|
|
2192
|
+
while (true) {
|
|
2193
|
+
const dateKey = checkDate.toISOString().split('T')[0];
|
|
2194
|
+
if (activeDates.has(dateKey)) {
|
|
2195
|
+
currentStreak++;
|
|
2196
|
+
checkDate.setDate(checkDate.getDate() - 1);
|
|
2197
|
+
} else {
|
|
2198
|
+
break;
|
|
2199
|
+
}
|
|
2200
|
+
}
|
|
2201
|
+
}
|
|
2202
|
+
|
|
2203
|
+
// Calculate longest streak by checking all consecutive days
|
|
2204
|
+
const oneYearAgo = new Date(today);
|
|
2205
|
+
oneYearAgo.setFullYear(today.getFullYear() - 1);
|
|
2206
|
+
|
|
2207
|
+
checkDate = new Date(oneYearAgo);
|
|
2208
|
+
while (checkDate <= today) {
|
|
2209
|
+
const dateKey = checkDate.toISOString().split('T')[0];
|
|
2210
|
+
|
|
2211
|
+
if (activeDates.has(dateKey)) {
|
|
2212
|
+
tempStreak++;
|
|
2213
|
+
longestStreak = Math.max(longestStreak, tempStreak);
|
|
2214
|
+
} else {
|
|
2215
|
+
tempStreak = 0;
|
|
2216
|
+
}
|
|
2217
|
+
|
|
2218
|
+
checkDate.setDate(checkDate.getDate() + 1);
|
|
2219
|
+
}
|
|
2220
|
+
|
|
2221
|
+
return { longestStreak, currentStreak };
|
|
2222
|
+
}
|
|
2036
2223
|
|
|
2037
2224
|
stop() {
|
|
2038
2225
|
// Stop file watchers
|