claude-code-templates 1.26.0 → 1.26.2
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/chats_mobile.html +475 -3
- package/src/chats-mobile.js +284 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-code-templates",
|
|
3
|
-
"version": "1.26.
|
|
3
|
+
"version": "1.26.2",
|
|
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": {
|
|
@@ -782,6 +782,15 @@
|
|
|
782
782
|
stroke: rgba(251, 146, 60, 1);
|
|
783
783
|
}
|
|
784
784
|
|
|
785
|
+
.action-btn.analytics-btn:hover {
|
|
786
|
+
background: rgba(139, 92, 246, 0.15);
|
|
787
|
+
color: rgba(139, 92, 246, 1);
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
.action-btn.analytics-btn:hover svg {
|
|
791
|
+
stroke: rgba(139, 92, 246, 1);
|
|
792
|
+
}
|
|
793
|
+
|
|
785
794
|
/* Conversations list */
|
|
786
795
|
.conversations-list {
|
|
787
796
|
flex: 1;
|
|
@@ -2067,6 +2076,14 @@
|
|
|
2067
2076
|
</svg>
|
|
2068
2077
|
<span>Search</span>
|
|
2069
2078
|
</button>
|
|
2079
|
+
<button class="action-btn analytics-btn" id="showAnalytics" onclick="showAnalyticsModal()">
|
|
2080
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
2081
|
+
<line x1="18" y1="20" x2="18" y2="10"></line>
|
|
2082
|
+
<line x1="12" y1="20" x2="12" y2="4"></line>
|
|
2083
|
+
<line x1="6" y1="20" x2="6" y2="14"></line>
|
|
2084
|
+
</svg>
|
|
2085
|
+
<span>Analytics</span>
|
|
2086
|
+
</button>
|
|
2070
2087
|
</div>
|
|
2071
2088
|
</div>
|
|
2072
2089
|
<div class="header-right">
|
|
@@ -2169,13 +2186,14 @@
|
|
|
2169
2186
|
</p>
|
|
2170
2187
|
|
|
2171
2188
|
<div style="margin-bottom: 10px;">
|
|
2172
|
-
<p style="margin: 0 0 4px 0; font-size: 12px; font-weight: 600; color: #10b981;">Option 1:
|
|
2189
|
+
<p style="margin: 0 0 4px 0; font-size: 12px; font-weight: 600; color: #10b981;">Option 1: Start with context loaded</p>
|
|
2173
2190
|
<code style="display: block; background: rgba(0,0,0,0.5); padding: 8px; border-radius: 4px; font-size: 12px; color: #e5e7eb; overflow-x: auto; white-space: nowrap;">claude "read @context-file.md and continue"</code>
|
|
2174
2191
|
</div>
|
|
2175
2192
|
|
|
2176
2193
|
<div>
|
|
2177
|
-
<p style="margin: 0 0 4px 0; font-size: 12px; font-weight: 600; color: #10b981;">Option 2:
|
|
2178
|
-
<code style="display: block; background: rgba(0,0,0,0.5); padding: 8px; border-radius: 4px; font-size: 12px; color: #e5e7eb; overflow-x: auto; white-space: nowrap;">
|
|
2194
|
+
<p style="margin: 0 0 4px 0; font-size: 12px; font-weight: 600; color: #10b981;">Option 2: In interactive mode</p>
|
|
2195
|
+
<code style="display: block; background: rgba(0,0,0,0.5); padding: 8px; border-radius: 4px; font-size: 12px; color: #e5e7eb; overflow-x: auto; white-space: nowrap;">claude</code>
|
|
2196
|
+
<code style="display: block; background: rgba(0,0,0,0.5); padding: 8px; border-radius: 4px; font-size: 12px; color: #e5e7eb; overflow-x: auto; white-space: nowrap; margin-top: 4px;">> @context-file.md continue with the task</code>
|
|
2179
2197
|
</div>
|
|
2180
2198
|
</div>
|
|
2181
2199
|
|
|
@@ -2195,6 +2213,176 @@
|
|
|
2195
2213
|
</div>
|
|
2196
2214
|
</div>
|
|
2197
2215
|
|
|
2216
|
+
<!-- Analytics Modal -->
|
|
2217
|
+
<div class="modal-overlay" id="analyticsModal">
|
|
2218
|
+
<div class="modal" style="max-width: 900px; max-height: 90vh; overflow-y: auto;">
|
|
2219
|
+
<div class="modal-header">
|
|
2220
|
+
<h3 class="modal-title">Session Analytics (Beta)</h3>
|
|
2221
|
+
</div>
|
|
2222
|
+
<div style="padding: 20px;">
|
|
2223
|
+
<!-- Loading State -->
|
|
2224
|
+
<div id="analyticsLoading" style="text-align: center; padding: 40px;">
|
|
2225
|
+
<p style="color: var(--text-secondary);">Loading analytics data...</p>
|
|
2226
|
+
</div>
|
|
2227
|
+
|
|
2228
|
+
<!-- Analytics Content (hidden initially) -->
|
|
2229
|
+
<div id="analyticsContent" style="display: none;">
|
|
2230
|
+
<!-- Overview Stats - 3 columns -->
|
|
2231
|
+
<div style="display: grid; grid-template-columns: repeat(3, 1fr); gap: 12px; margin-bottom: 20px;">
|
|
2232
|
+
<div style="background: rgba(139, 92, 246, 0.1); border-radius: 8px; padding: 12px; border-left: 3px solid #8b5cf6;">
|
|
2233
|
+
<div style="font-size: 11px; color: var(--text-secondary); margin-bottom: 4px;">Total Messages</div>
|
|
2234
|
+
<div style="font-size: 24px; font-weight: 700; color: #8b5cf6;" id="analyticsMessageCount">-</div>
|
|
2235
|
+
</div>
|
|
2236
|
+
<div style="background: rgba(59, 130, 246, 0.1); border-radius: 8px; padding: 12px; border-left: 3px solid #3b82f6;">
|
|
2237
|
+
<div style="font-size: 11px; color: var(--text-secondary); margin-bottom: 4px;">Total Tokens</div>
|
|
2238
|
+
<div style="font-size: 24px; font-weight: 700; color: #3b82f6;" id="analyticsTotalTokens">-</div>
|
|
2239
|
+
</div>
|
|
2240
|
+
<div style="background: rgba(16, 185, 129, 0.1); border-radius: 8px; padding: 12px; border-left: 3px solid #10b981;">
|
|
2241
|
+
<div style="font-size: 11px; color: var(--text-secondary); margin-bottom: 4px;">Tool Calls</div>
|
|
2242
|
+
<div style="font-size: 24px; font-weight: 700; color: #10b981;" id="analyticsToolCalls">-</div>
|
|
2243
|
+
</div>
|
|
2244
|
+
<div style="background: rgba(234, 88, 12, 0.1); border-radius: 8px; padding: 12px; border-left: 3px solid #ea580c;">
|
|
2245
|
+
<div style="font-size: 11px; color: var(--text-secondary); margin-bottom: 4px;">Tools Used</div>
|
|
2246
|
+
<div style="font-size: 24px; font-weight: 700; color: #ea580c;" id="analyticsUniqueTools">-</div>
|
|
2247
|
+
</div>
|
|
2248
|
+
<div style="background: rgba(251, 146, 60, 0.1); border-radius: 8px; padding: 12px; border-left: 3px solid #fb923c;">
|
|
2249
|
+
<div style="font-size: 11px; color: var(--text-secondary); margin-bottom: 4px;">Models Used</div>
|
|
2250
|
+
<div style="font-size: 24px; font-weight: 700; color: #fb923c;" id="analyticsUniqueModels">-</div>
|
|
2251
|
+
</div>
|
|
2252
|
+
<div style="background: rgba(168, 85, 247, 0.1); border-radius: 8px; padding: 12px; border-left: 3px solid #a855f7;">
|
|
2253
|
+
<div style="font-size: 11px; color: var(--text-secondary); margin-bottom: 4px;">Cache Efficiency</div>
|
|
2254
|
+
<div style="font-size: 24px; font-weight: 700; color: #a855f7;" id="analyticsCacheEfficiency">-</div>
|
|
2255
|
+
</div>
|
|
2256
|
+
</div>
|
|
2257
|
+
|
|
2258
|
+
<!-- Token Usage Breakdown -->
|
|
2259
|
+
<div style="background: rgba(255, 255, 255, 0.03); border-radius: 8px; padding: 16px; margin-bottom: 16px;">
|
|
2260
|
+
<h4 style="margin: 0 0 12px 0; color: var(--text-primary); font-size: 14px;">
|
|
2261
|
+
Token Usage Breakdown
|
|
2262
|
+
</h4>
|
|
2263
|
+
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 12px;">
|
|
2264
|
+
<div>
|
|
2265
|
+
<div style="font-size: 12px; color: var(--text-secondary); margin-bottom: 4px;">Input Tokens</div>
|
|
2266
|
+
<div style="font-size: 18px; font-weight: 600; color: #3b82f6;" id="analyticsInputTokens">-</div>
|
|
2267
|
+
</div>
|
|
2268
|
+
<div>
|
|
2269
|
+
<div style="font-size: 12px; color: var(--text-secondary); margin-bottom: 4px;">Output Tokens</div>
|
|
2270
|
+
<div style="font-size: 18px; font-weight: 600; color: #8b5cf6;" id="analyticsOutputTokens">-</div>
|
|
2271
|
+
</div>
|
|
2272
|
+
<div>
|
|
2273
|
+
<div style="font-size: 12px; color: var(--text-secondary); margin-bottom: 4px;">Cache Creation</div>
|
|
2274
|
+
<div style="font-size: 18px; font-weight: 600; color: #fb923c;" id="analyticsCacheCreation">-</div>
|
|
2275
|
+
</div>
|
|
2276
|
+
<div>
|
|
2277
|
+
<div style="font-size: 12px; color: var(--text-secondary); margin-bottom: 4px;">Cache Reads</div>
|
|
2278
|
+
<div style="font-size: 18px; font-weight: 600; color: #10b981;" id="analyticsCacheReads">-</div>
|
|
2279
|
+
</div>
|
|
2280
|
+
</div>
|
|
2281
|
+
<div style="margin-top: 12px; padding-top: 12px; border-top: 1px solid rgba(255,255,255,0.1);">
|
|
2282
|
+
<div style="font-size: 11px; color: var(--text-secondary); margin-bottom: 4px;">Cost Estimate (USD)</div>
|
|
2283
|
+
<div style="font-size: 16px; font-weight: 600; color: #10b981;" id="analyticsCostEstimate">$0.00</div>
|
|
2284
|
+
</div>
|
|
2285
|
+
</div>
|
|
2286
|
+
|
|
2287
|
+
<!-- Session Timeline -->
|
|
2288
|
+
<div style="background: rgba(255, 255, 255, 0.03); border-radius: 8px; padding: 16px; margin-bottom: 16px;">
|
|
2289
|
+
<h4 style="margin: 0 0 12px 0; color: var(--text-primary); font-size: 14px;">
|
|
2290
|
+
Session Timeline
|
|
2291
|
+
</h4>
|
|
2292
|
+
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 12px;">
|
|
2293
|
+
<div>
|
|
2294
|
+
<div style="font-size: 12px; color: var(--text-secondary); margin-bottom: 4px;">Started</div>
|
|
2295
|
+
<div style="font-size: 14px; font-weight: 600; color: var(--text-primary);" id="analyticsStartTime">-</div>
|
|
2296
|
+
</div>
|
|
2297
|
+
<div>
|
|
2298
|
+
<div style="font-size: 12px; color: var(--text-secondary); margin-bottom: 4px;">Last Activity</div>
|
|
2299
|
+
<div style="font-size: 14px; font-weight: 600; color: var(--text-primary);" id="analyticsLastActivity">-</div>
|
|
2300
|
+
</div>
|
|
2301
|
+
<div>
|
|
2302
|
+
<div style="font-size: 12px; color: var(--text-secondary); margin-bottom: 4px;">Duration</div>
|
|
2303
|
+
<div style="font-size: 14px; font-weight: 600; color: var(--text-primary);" id="analyticsDuration">-</div>
|
|
2304
|
+
</div>
|
|
2305
|
+
<div>
|
|
2306
|
+
<div style="font-size: 12px; color: var(--text-secondary); margin-bottom: 4px;">Status</div>
|
|
2307
|
+
<div style="font-size: 14px; font-weight: 600; color: var(--text-primary);" id="analyticsStatus">-</div>
|
|
2308
|
+
</div>
|
|
2309
|
+
</div>
|
|
2310
|
+
</div>
|
|
2311
|
+
|
|
2312
|
+
<!-- Time Breakdown -->
|
|
2313
|
+
<div style="background: rgba(255, 255, 255, 0.03); border-radius: 8px; padding: 16px; margin-bottom: 16px;">
|
|
2314
|
+
<h4 style="margin: 0 0 12px 0; color: var(--text-primary); font-size: 14px;">
|
|
2315
|
+
Time Breakdown
|
|
2316
|
+
</h4>
|
|
2317
|
+
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 12px; margin-bottom: 12px;">
|
|
2318
|
+
<div>
|
|
2319
|
+
<div style="font-size: 12px; color: var(--text-secondary); margin-bottom: 4px;">Claude Executing</div>
|
|
2320
|
+
<div style="font-size: 18px; font-weight: 600; color: #3b82f6;" id="analyticsWaitTime">-</div>
|
|
2321
|
+
<div style="font-size: 11px; color: var(--text-secondary); margin-top: 2px;" id="analyticsWaitPercent">-</div>
|
|
2322
|
+
</div>
|
|
2323
|
+
<div>
|
|
2324
|
+
<div style="font-size: 12px; color: var(--text-secondary); margin-bottom: 4px;">User Conversing</div>
|
|
2325
|
+
<div style="font-size: 18px; font-weight: 600; color: #10b981;" id="analyticsUserTime">-</div>
|
|
2326
|
+
<div style="font-size: 11px; color: var(--text-secondary); margin-top: 2px;" id="analyticsUserPercent">-</div>
|
|
2327
|
+
</div>
|
|
2328
|
+
</div>
|
|
2329
|
+
<div style="height: 8px; background: rgba(255,255,255,0.1); border-radius: 4px; overflow: hidden; display: flex;">
|
|
2330
|
+
<div id="analyticsWaitBar" style="background: linear-gradient(90deg, #3b82f6, #2563eb); transition: width 0.3s ease;"></div>
|
|
2331
|
+
<div id="analyticsUserBar" style="background: linear-gradient(90deg, #10b981, #059669); transition: width 0.3s ease;"></div>
|
|
2332
|
+
</div>
|
|
2333
|
+
</div>
|
|
2334
|
+
|
|
2335
|
+
<!-- Model Information -->
|
|
2336
|
+
<div style="background: rgba(255, 255, 255, 0.03); border-radius: 8px; padding: 16px; margin-bottom: 16px;">
|
|
2337
|
+
<h4 style="margin: 0 0 12px 0; color: var(--text-primary); font-size: 14px;">
|
|
2338
|
+
Model Information
|
|
2339
|
+
</h4>
|
|
2340
|
+
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 12px;">
|
|
2341
|
+
<div>
|
|
2342
|
+
<div style="font-size: 12px; color: var(--text-secondary); margin-bottom: 4px;">Primary Model</div>
|
|
2343
|
+
<div style="font-size: 14px; font-weight: 600; color: var(--text-primary); font-family: monospace;" id="analyticsPrimaryModel">-</div>
|
|
2344
|
+
</div>
|
|
2345
|
+
<div>
|
|
2346
|
+
<div style="font-size: 12px; color: var(--text-secondary); margin-bottom: 4px;">Service Tier</div>
|
|
2347
|
+
<div style="font-size: 14px; font-weight: 600; color: var(--text-primary);" id="analyticsServiceTier">-</div>
|
|
2348
|
+
</div>
|
|
2349
|
+
</div>
|
|
2350
|
+
<div id="analyticsAllModels" style="display: none; margin-top: 12px;">
|
|
2351
|
+
<div style="font-size: 12px; color: var(--text-secondary); margin-bottom: 6px;">All Models Used</div>
|
|
2352
|
+
<div id="analyticsModelsList" style="display: flex; flex-wrap: wrap; gap: 6px;">
|
|
2353
|
+
<!-- Models will be populated here -->
|
|
2354
|
+
</div>
|
|
2355
|
+
</div>
|
|
2356
|
+
</div>
|
|
2357
|
+
|
|
2358
|
+
<!-- Tool Usage -->
|
|
2359
|
+
<div style="background: rgba(255, 255, 255, 0.03); border-radius: 8px; padding: 16px; margin-bottom: 16px;">
|
|
2360
|
+
<h4 style="margin: 0 0 12px 0; color: var(--text-primary); font-size: 14px;">
|
|
2361
|
+
Tool Usage
|
|
2362
|
+
</h4>
|
|
2363
|
+
<div id="analyticsToolsList" style="max-height: 200px; overflow-y: auto;">
|
|
2364
|
+
<!-- Tool usage will be populated here -->
|
|
2365
|
+
</div>
|
|
2366
|
+
</div>
|
|
2367
|
+
|
|
2368
|
+
<!-- Components Used -->
|
|
2369
|
+
<div id="analyticsComponentsSection" style="background: rgba(255, 255, 255, 0.03); border-radius: 8px; padding: 16px; margin-bottom: 16px; display: none;">
|
|
2370
|
+
<h4 style="margin: 0 0 12px 0; color: var(--text-primary); font-size: 14px;">
|
|
2371
|
+
Components Used
|
|
2372
|
+
</h4>
|
|
2373
|
+
<div id="analyticsComponentsContent">
|
|
2374
|
+
<!-- Components will be populated here -->
|
|
2375
|
+
</div>
|
|
2376
|
+
</div>
|
|
2377
|
+
</div>
|
|
2378
|
+
</div>
|
|
2379
|
+
|
|
2380
|
+
<div class="modal-actions">
|
|
2381
|
+
<button class="modal-btn secondary" onclick="closeAnalyticsModal()">Close</button>
|
|
2382
|
+
</div>
|
|
2383
|
+
</div>
|
|
2384
|
+
</div>
|
|
2385
|
+
|
|
2198
2386
|
<!-- Import WebSocket and Data Services -->
|
|
2199
2387
|
<script src="services/WebSocketService.js"></script>
|
|
2200
2388
|
<script src="services/DataService.js"></script>
|
|
@@ -2600,8 +2788,10 @@
|
|
|
2600
2788
|
|
|
2601
2789
|
const resumeBtn = document.getElementById('resumeConversation');
|
|
2602
2790
|
const downloadBtn = document.getElementById('downloadConversation');
|
|
2791
|
+
const analyticsBtn = document.getElementById('showAnalytics');
|
|
2603
2792
|
resumeBtn.setAttribute('data-conversation-id', conversationId);
|
|
2604
2793
|
downloadBtn.setAttribute('data-conversation-id', conversationId);
|
|
2794
|
+
analyticsBtn.setAttribute('data-conversation-id', conversationId);
|
|
2605
2795
|
|
|
2606
2796
|
// Load messages (placeholder for now)
|
|
2607
2797
|
this.loadChatMessages(conversationId);
|
|
@@ -4761,6 +4951,288 @@
|
|
|
4761
4951
|
}
|
|
4762
4952
|
}
|
|
4763
4953
|
|
|
4954
|
+
// Analytics Modal Functions
|
|
4955
|
+
async function showAnalyticsModal() {
|
|
4956
|
+
const analyticsBtn = document.getElementById('showAnalytics');
|
|
4957
|
+
const conversationId = analyticsBtn.getAttribute('data-conversation-id');
|
|
4958
|
+
|
|
4959
|
+
if (!conversationId) {
|
|
4960
|
+
console.error('No conversation ID found');
|
|
4961
|
+
return;
|
|
4962
|
+
}
|
|
4963
|
+
|
|
4964
|
+
console.log('📊 Opening analytics modal for conversation:', conversationId);
|
|
4965
|
+
|
|
4966
|
+
// Show analytics modal
|
|
4967
|
+
const analyticsModal = document.getElementById('analyticsModal');
|
|
4968
|
+
analyticsModal.classList.add('show');
|
|
4969
|
+
|
|
4970
|
+
// Reset to loading state
|
|
4971
|
+
document.getElementById('analyticsLoading').style.display = 'block';
|
|
4972
|
+
document.getElementById('analyticsContent').style.display = 'none';
|
|
4973
|
+
|
|
4974
|
+
// Close modal when clicking outside
|
|
4975
|
+
analyticsModal.addEventListener('click', (e) => {
|
|
4976
|
+
if (e.target === analyticsModal) {
|
|
4977
|
+
closeAnalyticsModal();
|
|
4978
|
+
}
|
|
4979
|
+
});
|
|
4980
|
+
|
|
4981
|
+
// Fetch analytics data
|
|
4982
|
+
try {
|
|
4983
|
+
const response = await fetch(`/api/conversations/${conversationId}/analytics`);
|
|
4984
|
+
|
|
4985
|
+
if (!response.ok) {
|
|
4986
|
+
throw new Error(`Failed to fetch analytics: ${response.statusText}`);
|
|
4987
|
+
}
|
|
4988
|
+
|
|
4989
|
+
const data = await response.json();
|
|
4990
|
+
console.log('✅ Analytics fetched:', data);
|
|
4991
|
+
|
|
4992
|
+
// Populate modal with analytics data
|
|
4993
|
+
populateAnalyticsModal(data.analytics);
|
|
4994
|
+
|
|
4995
|
+
// Hide loading, show content
|
|
4996
|
+
document.getElementById('analyticsLoading').style.display = 'none';
|
|
4997
|
+
document.getElementById('analyticsContent').style.display = 'block';
|
|
4998
|
+
|
|
4999
|
+
} catch (error) {
|
|
5000
|
+
console.error('❌ Failed to fetch analytics:', error);
|
|
5001
|
+
document.getElementById('analyticsLoading').innerHTML =
|
|
5002
|
+
`<p style="color: #ef4444;">Failed to load analytics: ${error.message}</p>`;
|
|
5003
|
+
}
|
|
5004
|
+
}
|
|
5005
|
+
|
|
5006
|
+
function closeAnalyticsModal() {
|
|
5007
|
+
const analyticsModal = document.getElementById('analyticsModal');
|
|
5008
|
+
analyticsModal.classList.remove('show');
|
|
5009
|
+
}
|
|
5010
|
+
|
|
5011
|
+
function populateAnalyticsModal(analytics) {
|
|
5012
|
+
console.log('📊 Populating analytics modal with data:', analytics);
|
|
5013
|
+
|
|
5014
|
+
// Overview stats - with safe fallbacks
|
|
5015
|
+
document.getElementById('analyticsMessageCount').textContent = (analytics.messageCount || 0).toLocaleString();
|
|
5016
|
+
document.getElementById('analyticsTotalTokens').textContent = (analytics.totalTokens || 0).toLocaleString();
|
|
5017
|
+
document.getElementById('analyticsToolCalls').textContent = (analytics.toolCalls || 0).toLocaleString();
|
|
5018
|
+
document.getElementById('analyticsCacheEfficiency').textContent = analytics.cacheEfficiency || '0%';
|
|
5019
|
+
|
|
5020
|
+
// New stats: unique tools and models
|
|
5021
|
+
const uniqueTools = analytics.toolUsage?.uniqueTools || 0;
|
|
5022
|
+
const uniqueModels = (analytics.modelInfo?.modelUsage && Array.isArray(analytics.modelInfo.modelUsage))
|
|
5023
|
+
? analytics.modelInfo.modelUsage.length
|
|
5024
|
+
: 0;
|
|
5025
|
+
|
|
5026
|
+
console.log('📊 Unique tools:', uniqueTools, 'Unique models:', uniqueModels);
|
|
5027
|
+
document.getElementById('analyticsUniqueTools').textContent = uniqueTools;
|
|
5028
|
+
document.getElementById('analyticsUniqueModels').textContent = uniqueModels;
|
|
5029
|
+
|
|
5030
|
+
// Token breakdown
|
|
5031
|
+
document.getElementById('analyticsInputTokens').textContent = analytics.tokenUsage.inputTokens.toLocaleString();
|
|
5032
|
+
document.getElementById('analyticsOutputTokens').textContent = analytics.tokenUsage.outputTokens.toLocaleString();
|
|
5033
|
+
document.getElementById('analyticsCacheCreation').textContent = analytics.tokenUsage.cacheCreationTokens.toLocaleString();
|
|
5034
|
+
document.getElementById('analyticsCacheReads').textContent = analytics.tokenUsage.cacheReadTokens.toLocaleString();
|
|
5035
|
+
|
|
5036
|
+
// Cost estimate
|
|
5037
|
+
document.getElementById('analyticsCostEstimate').textContent = `$${analytics.costEstimate.total}`;
|
|
5038
|
+
|
|
5039
|
+
// Model info
|
|
5040
|
+
document.getElementById('analyticsPrimaryModel').textContent = analytics.modelInfo.primaryModel;
|
|
5041
|
+
document.getElementById('analyticsServiceTier').textContent = analytics.modelInfo.serviceTier;
|
|
5042
|
+
|
|
5043
|
+
// Show all models if multiple were used with usage percentages
|
|
5044
|
+
const allModelsSection = document.getElementById('analyticsAllModels');
|
|
5045
|
+
if (analytics.modelInfo.hasMultipleModels && analytics.modelInfo.modelUsage && analytics.modelInfo.modelUsage.length > 1) {
|
|
5046
|
+
const modelsListHTML = analytics.modelInfo.modelUsage
|
|
5047
|
+
.map(usage => `
|
|
5048
|
+
<span style="
|
|
5049
|
+
display: inline-flex;
|
|
5050
|
+
align-items: center;
|
|
5051
|
+
gap: 6px;
|
|
5052
|
+
padding: 4px 10px;
|
|
5053
|
+
background: rgba(139, 92, 246, 0.15);
|
|
5054
|
+
border: 1px solid rgba(139, 92, 246, 0.3);
|
|
5055
|
+
border-radius: 4px;
|
|
5056
|
+
font-size: 12px;
|
|
5057
|
+
font-family: monospace;
|
|
5058
|
+
color: var(--text-primary);
|
|
5059
|
+
">
|
|
5060
|
+
<span>${usage.model}</span>
|
|
5061
|
+
<span style="
|
|
5062
|
+
padding: 2px 6px;
|
|
5063
|
+
background: rgba(139, 92, 246, 0.3);
|
|
5064
|
+
border-radius: 3px;
|
|
5065
|
+
font-size: 10px;
|
|
5066
|
+
font-weight: 600;
|
|
5067
|
+
color: #e9d5ff;
|
|
5068
|
+
">${usage.count} msgs (${usage.percentage}%)</span>
|
|
5069
|
+
</span>
|
|
5070
|
+
`)
|
|
5071
|
+
.join('');
|
|
5072
|
+
document.getElementById('analyticsModelsList').innerHTML = modelsListHTML;
|
|
5073
|
+
allModelsSection.style.display = 'block';
|
|
5074
|
+
} else {
|
|
5075
|
+
allModelsSection.style.display = 'none';
|
|
5076
|
+
}
|
|
5077
|
+
|
|
5078
|
+
// Tool usage breakdown
|
|
5079
|
+
const toolsList = document.getElementById('analyticsToolsList');
|
|
5080
|
+
if (Object.keys(analytics.toolUsage.breakdown).length > 0) {
|
|
5081
|
+
const toolsHTML = Object.entries(analytics.toolUsage.breakdown)
|
|
5082
|
+
.sort((a, b) => b[1] - a[1]) // Sort by usage count
|
|
5083
|
+
.map(([tool, count]) => {
|
|
5084
|
+
const percentage = Math.round((count / analytics.toolUsage.totalCalls) * 100);
|
|
5085
|
+
return `
|
|
5086
|
+
<div style="margin-bottom: 10px; padding: 8px; background: rgba(255,255,255,0.03); border-radius: 6px;">
|
|
5087
|
+
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 4px;">
|
|
5088
|
+
<span style="font-size: 13px; font-weight: 600; color: var(--text-primary);">${tool}</span>
|
|
5089
|
+
<span style="font-size: 12px; color: var(--text-secondary);">${count} calls (${percentage}%)</span>
|
|
5090
|
+
</div>
|
|
5091
|
+
<div style="height: 4px; background: rgba(255,255,255,0.1); border-radius: 2px; overflow: hidden;">
|
|
5092
|
+
<div style="height: 100%; width: ${percentage}%; background: linear-gradient(90deg, #10b981, #059669); transition: width 0.3s ease;"></div>
|
|
5093
|
+
</div>
|
|
5094
|
+
</div>
|
|
5095
|
+
`;
|
|
5096
|
+
})
|
|
5097
|
+
.join('');
|
|
5098
|
+
toolsList.innerHTML = toolsHTML;
|
|
5099
|
+
} else {
|
|
5100
|
+
toolsList.innerHTML = '<p style="color: var(--text-secondary); font-size: 13px; text-align: center; padding: 20px;">No tool usage detected in this session</p>';
|
|
5101
|
+
}
|
|
5102
|
+
|
|
5103
|
+
// Timeline
|
|
5104
|
+
const formatDate = (isoString) => {
|
|
5105
|
+
if (!isoString) return 'N/A';
|
|
5106
|
+
const date = new Date(isoString);
|
|
5107
|
+
return date.toLocaleString('en-US', {
|
|
5108
|
+
month: 'short',
|
|
5109
|
+
day: 'numeric',
|
|
5110
|
+
hour: '2-digit',
|
|
5111
|
+
minute: '2-digit'
|
|
5112
|
+
});
|
|
5113
|
+
};
|
|
5114
|
+
|
|
5115
|
+
document.getElementById('analyticsStartTime').textContent = formatDate(analytics.timeline.startTime);
|
|
5116
|
+
document.getElementById('analyticsLastActivity').textContent = formatDate(analytics.timeline.endTime);
|
|
5117
|
+
document.getElementById('analyticsDuration').textContent = analytics.timeline.duration;
|
|
5118
|
+
document.getElementById('analyticsStatus').textContent = analytics.timeline.status.charAt(0).toUpperCase() + analytics.timeline.status.slice(1);
|
|
5119
|
+
|
|
5120
|
+
// Time breakdown
|
|
5121
|
+
if (analytics.timeBreakdown && analytics.timeBreakdown.totalWaitTime) {
|
|
5122
|
+
document.getElementById('analyticsWaitTime').textContent = analytics.timeBreakdown.totalWaitTime || '0s';
|
|
5123
|
+
document.getElementById('analyticsUserTime').textContent = analytics.timeBreakdown.totalUserTime || '0s';
|
|
5124
|
+
document.getElementById('analyticsWaitPercent').textContent = `${analytics.timeBreakdown.waitTimePercent || 0}% of iteration time`;
|
|
5125
|
+
document.getElementById('analyticsUserPercent').textContent = `${analytics.timeBreakdown.userTimePercent || 0}% of iteration time`;
|
|
5126
|
+
|
|
5127
|
+
// Update progress bars
|
|
5128
|
+
document.getElementById('analyticsWaitBar').style.width = `${analytics.timeBreakdown.waitTimePercent || 0}%`;
|
|
5129
|
+
document.getElementById('analyticsUserBar').style.width = `${analytics.timeBreakdown.userTimePercent || 0}%`;
|
|
5130
|
+
} else {
|
|
5131
|
+
// No timing data available
|
|
5132
|
+
document.getElementById('analyticsWaitTime').textContent = 'N/A';
|
|
5133
|
+
document.getElementById('analyticsUserTime').textContent = 'N/A';
|
|
5134
|
+
document.getElementById('analyticsWaitPercent').textContent = 'No data';
|
|
5135
|
+
document.getElementById('analyticsUserPercent').textContent = 'No data';
|
|
5136
|
+
document.getElementById('analyticsWaitBar').style.width = '0%';
|
|
5137
|
+
document.getElementById('analyticsUserBar').style.width = '0%';
|
|
5138
|
+
}
|
|
5139
|
+
|
|
5140
|
+
// Components used (agents, commands, skills)
|
|
5141
|
+
if (analytics.componentsUsed) {
|
|
5142
|
+
const hasComponents = analytics.componentsUsed.totalAgents > 0 ||
|
|
5143
|
+
analytics.componentsUsed.totalCommands > 0 ||
|
|
5144
|
+
analytics.componentsUsed.totalSkills > 0;
|
|
5145
|
+
|
|
5146
|
+
if (hasComponents) {
|
|
5147
|
+
let componentsHTML = '';
|
|
5148
|
+
|
|
5149
|
+
// Agents
|
|
5150
|
+
if (analytics.componentsUsed.agents.length > 0) {
|
|
5151
|
+
componentsHTML += '<div style="margin-bottom: 12px;">';
|
|
5152
|
+
componentsHTML += '<div style="font-size: 12px; color: var(--text-secondary); margin-bottom: 6px; font-weight: 600;">Agents</div>';
|
|
5153
|
+
componentsHTML += '<div style="display: flex; flex-wrap: wrap; gap: 6px;">';
|
|
5154
|
+
analytics.componentsUsed.agents.forEach(agent => {
|
|
5155
|
+
componentsHTML += `
|
|
5156
|
+
<span style="
|
|
5157
|
+
display: inline-flex;
|
|
5158
|
+
align-items: center;
|
|
5159
|
+
gap: 4px;
|
|
5160
|
+
padding: 4px 10px;
|
|
5161
|
+
background: rgba(139, 92, 246, 0.15);
|
|
5162
|
+
border: 1px solid rgba(139, 92, 246, 0.3);
|
|
5163
|
+
border-radius: 4px;
|
|
5164
|
+
font-size: 12px;
|
|
5165
|
+
color: var(--text-primary);
|
|
5166
|
+
">
|
|
5167
|
+
<span>${agent.type}</span>
|
|
5168
|
+
<span style="color: var(--text-secondary); font-size: 11px;">×${agent.count}</span>
|
|
5169
|
+
</span>
|
|
5170
|
+
`;
|
|
5171
|
+
});
|
|
5172
|
+
componentsHTML += '</div></div>';
|
|
5173
|
+
}
|
|
5174
|
+
|
|
5175
|
+
// Slash Commands
|
|
5176
|
+
if (analytics.componentsUsed.slashCommands.length > 0) {
|
|
5177
|
+
componentsHTML += '<div style="margin-bottom: 12px;">';
|
|
5178
|
+
componentsHTML += '<div style="font-size: 12px; color: var(--text-secondary); margin-bottom: 6px; font-weight: 600;">Slash Commands</div>';
|
|
5179
|
+
componentsHTML += '<div style="display: flex; flex-wrap: wrap; gap: 6px;">';
|
|
5180
|
+
analytics.componentsUsed.slashCommands.forEach(cmd => {
|
|
5181
|
+
componentsHTML += `
|
|
5182
|
+
<span style="
|
|
5183
|
+
display: inline-flex;
|
|
5184
|
+
align-items: center;
|
|
5185
|
+
gap: 4px;
|
|
5186
|
+
padding: 4px 10px;
|
|
5187
|
+
background: rgba(59, 130, 246, 0.15);
|
|
5188
|
+
border: 1px solid rgba(59, 130, 246, 0.3);
|
|
5189
|
+
border-radius: 4px;
|
|
5190
|
+
font-size: 12px;
|
|
5191
|
+
font-family: monospace;
|
|
5192
|
+
color: var(--text-primary);
|
|
5193
|
+
">
|
|
5194
|
+
<span>${cmd.name}</span>
|
|
5195
|
+
<span style="color: var(--text-secondary); font-size: 11px;">×${cmd.count}</span>
|
|
5196
|
+
</span>
|
|
5197
|
+
`;
|
|
5198
|
+
});
|
|
5199
|
+
componentsHTML += '</div></div>';
|
|
5200
|
+
}
|
|
5201
|
+
|
|
5202
|
+
// Skills
|
|
5203
|
+
if (analytics.componentsUsed.skills.length > 0) {
|
|
5204
|
+
componentsHTML += '<div>';
|
|
5205
|
+
componentsHTML += '<div style="font-size: 12px; color: var(--text-secondary); margin-bottom: 6px; font-weight: 600;">Skills</div>';
|
|
5206
|
+
componentsHTML += '<div style="display: flex; flex-wrap: wrap; gap: 6px;">';
|
|
5207
|
+
analytics.componentsUsed.skills.forEach(skill => {
|
|
5208
|
+
componentsHTML += `
|
|
5209
|
+
<span style="
|
|
5210
|
+
display: inline-flex;
|
|
5211
|
+
align-items: center;
|
|
5212
|
+
gap: 4px;
|
|
5213
|
+
padding: 4px 10px;
|
|
5214
|
+
background: rgba(16, 185, 129, 0.15);
|
|
5215
|
+
border: 1px solid rgba(16, 185, 129, 0.3);
|
|
5216
|
+
border-radius: 4px;
|
|
5217
|
+
font-size: 12px;
|
|
5218
|
+
color: var(--text-primary);
|
|
5219
|
+
">
|
|
5220
|
+
<span>${skill.name}</span>
|
|
5221
|
+
<span style="color: var(--text-secondary); font-size: 11px;">×${skill.count}</span>
|
|
5222
|
+
</span>
|
|
5223
|
+
`;
|
|
5224
|
+
});
|
|
5225
|
+
componentsHTML += '</div></div>';
|
|
5226
|
+
}
|
|
5227
|
+
|
|
5228
|
+
document.getElementById('analyticsComponentsContent').innerHTML = componentsHTML;
|
|
5229
|
+
document.getElementById('analyticsComponentsSection').style.display = 'block';
|
|
5230
|
+
} else {
|
|
5231
|
+
document.getElementById('analyticsComponentsSection').style.display = 'none';
|
|
5232
|
+
}
|
|
5233
|
+
}
|
|
5234
|
+
}
|
|
5235
|
+
|
|
4764
5236
|
// Initialize the app
|
|
4765
5237
|
document.addEventListener('DOMContentLoaded', () => {
|
|
4766
5238
|
new ChatsMobileApp();
|
package/src/chats-mobile.js
CHANGED
|
@@ -9,6 +9,7 @@ const ConversationAnalyzer = require('./analytics/core/ConversationAnalyzer');
|
|
|
9
9
|
const StateCalculator = require('./analytics/core/StateCalculator');
|
|
10
10
|
const FileWatcher = require('./analytics/core/FileWatcher');
|
|
11
11
|
const DataCache = require('./analytics/data/DataCache');
|
|
12
|
+
const AgentAnalyzer = require('./analytics/core/AgentAnalyzer');
|
|
12
13
|
const WebSocketServer = require('./analytics/notifications/WebSocketServer');
|
|
13
14
|
const SessionSharing = require('./session-sharing');
|
|
14
15
|
|
|
@@ -512,6 +513,289 @@ class ChatsMobile {
|
|
|
512
513
|
}
|
|
513
514
|
});
|
|
514
515
|
|
|
516
|
+
// API to get detailed analytics for a conversation
|
|
517
|
+
this.app.get('/api/conversations/:id/analytics', async (req, res) => {
|
|
518
|
+
try {
|
|
519
|
+
const conversationId = req.params.id;
|
|
520
|
+
const conversation = this.data.conversations.find(conv => conv.id === conversationId);
|
|
521
|
+
|
|
522
|
+
if (!conversation) {
|
|
523
|
+
return res.status(404).json({ error: 'Conversation not found' });
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
console.log(chalk.cyan(`📊 Fetching analytics for conversation ${conversationId}...`));
|
|
527
|
+
|
|
528
|
+
// Get parsed messages for this conversation
|
|
529
|
+
const messages = await this.conversationAnalyzer.getParsedConversation(conversation.filePath);
|
|
530
|
+
|
|
531
|
+
// Calculate session duration and timing breakdown
|
|
532
|
+
const startTime = messages.length > 0 ? new Date(messages[0].timestamp) : null;
|
|
533
|
+
const endTime = messages.length > 0 ? new Date(messages[messages.length - 1].timestamp) : null;
|
|
534
|
+
const durationMs = startTime && endTime ? endTime - startTime : 0;
|
|
535
|
+
const durationHours = Math.floor(durationMs / (1000 * 60 * 60));
|
|
536
|
+
const durationMinutes = Math.floor((durationMs % (1000 * 60 * 60)) / (1000 * 60));
|
|
537
|
+
|
|
538
|
+
// Calculate time conversing vs executing (time between messages)
|
|
539
|
+
let totalWaitTime = 0; // Time waiting for Claude (thinking + executing)
|
|
540
|
+
let totalUserTime = 0; // Time user takes to respond
|
|
541
|
+
|
|
542
|
+
// Find user and assistant messages only (ignore tool results and other message types)
|
|
543
|
+
const conversationMessages = messages.filter(msg => msg.role === 'user' || msg.role === 'assistant');
|
|
544
|
+
|
|
545
|
+
let lastUserTime = null;
|
|
546
|
+
let lastAssistantTime = null;
|
|
547
|
+
|
|
548
|
+
conversationMessages.forEach(msg => {
|
|
549
|
+
if (msg.role === 'user') {
|
|
550
|
+
// If there was a previous assistant message, calculate user thinking time
|
|
551
|
+
if (lastAssistantTime) {
|
|
552
|
+
const thinkingTime = new Date(msg.timestamp) - lastAssistantTime;
|
|
553
|
+
// Only count gaps less than 1 hour to avoid counting long breaks
|
|
554
|
+
if (thinkingTime > 0 && thinkingTime < 60 * 60 * 1000) {
|
|
555
|
+
totalUserTime += thinkingTime;
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
lastUserTime = new Date(msg.timestamp);
|
|
559
|
+
} else if (msg.role === 'assistant') {
|
|
560
|
+
// If there was a previous user message, calculate Claude execution time
|
|
561
|
+
if (lastUserTime) {
|
|
562
|
+
const executionTime = new Date(msg.timestamp) - lastUserTime;
|
|
563
|
+
// Only count gaps less than 10 minutes (typical execution time)
|
|
564
|
+
if (executionTime > 0 && executionTime < 10 * 60 * 1000) {
|
|
565
|
+
totalWaitTime += executionTime;
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
lastAssistantTime = new Date(msg.timestamp);
|
|
569
|
+
}
|
|
570
|
+
});
|
|
571
|
+
|
|
572
|
+
const totalIterationTime = totalWaitTime + totalUserTime;
|
|
573
|
+
const waitTimePercent = totalIterationTime > 0 ? Math.round((totalWaitTime / totalIterationTime) * 100) : 0;
|
|
574
|
+
const userTimePercent = totalIterationTime > 0 ? Math.round((totalUserTime / totalIterationTime) * 100) : 0;
|
|
575
|
+
|
|
576
|
+
// Calculate cache efficiency
|
|
577
|
+
const cacheTotal = (conversation.tokenUsage?.cacheCreationTokens || 0) + (conversation.tokenUsage?.cacheReadTokens || 0);
|
|
578
|
+
const cacheEfficiency = cacheTotal > 0
|
|
579
|
+
? Math.round((conversation.tokenUsage?.cacheReadTokens || 0) / cacheTotal * 100)
|
|
580
|
+
: 0;
|
|
581
|
+
|
|
582
|
+
// Estimate cost (approximate Claude API pricing)
|
|
583
|
+
// Sonnet 4.5: $3/1M input, $15/1M output
|
|
584
|
+
// Cache write: $3.75/1M, Cache read: $0.30/1M
|
|
585
|
+
const inputCost = (conversation.tokenUsage?.inputTokens || 0) / 1000000 * 3;
|
|
586
|
+
const outputCost = (conversation.tokenUsage?.outputTokens || 0) / 1000000 * 15;
|
|
587
|
+
const cacheWriteCost = (conversation.tokenUsage?.cacheCreationTokens || 0) / 1000000 * 3.75;
|
|
588
|
+
const cacheReadCost = (conversation.tokenUsage?.cacheReadTokens || 0) / 1000000 * 0.30;
|
|
589
|
+
const totalCost = inputCost + outputCost + cacheWriteCost + cacheReadCost;
|
|
590
|
+
|
|
591
|
+
// Detect agents, hooks, and components used
|
|
592
|
+
const agentAnalyzer = new AgentAnalyzer();
|
|
593
|
+
const componentsUsed = {
|
|
594
|
+
agents: [],
|
|
595
|
+
slashCommands: [],
|
|
596
|
+
skills: []
|
|
597
|
+
};
|
|
598
|
+
|
|
599
|
+
messages.forEach(message => {
|
|
600
|
+
const messageContent = message.content;
|
|
601
|
+
const messageRole = message.role;
|
|
602
|
+
|
|
603
|
+
if (messageRole === 'assistant' && messageContent && Array.isArray(messageContent)) {
|
|
604
|
+
messageContent.forEach(content => {
|
|
605
|
+
// Detect Task tool with subagent_type (agents)
|
|
606
|
+
if (content.type === 'tool_use' && content.name === 'Task' && content.input?.subagent_type) {
|
|
607
|
+
const agentType = content.input.subagent_type;
|
|
608
|
+
if (!componentsUsed.agents.find(a => a.type === agentType)) {
|
|
609
|
+
componentsUsed.agents.push({
|
|
610
|
+
type: agentType,
|
|
611
|
+
count: 1
|
|
612
|
+
});
|
|
613
|
+
} else {
|
|
614
|
+
componentsUsed.agents.find(a => a.type === agentType).count++;
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
// Detect SlashCommand tool (commands)
|
|
619
|
+
if (content.type === 'tool_use' && content.name === 'SlashCommand' && content.input?.command) {
|
|
620
|
+
const command = content.input.command;
|
|
621
|
+
if (!componentsUsed.slashCommands.find(c => c.name === command)) {
|
|
622
|
+
componentsUsed.slashCommands.push({
|
|
623
|
+
name: command,
|
|
624
|
+
count: 1
|
|
625
|
+
});
|
|
626
|
+
} else {
|
|
627
|
+
componentsUsed.slashCommands.find(c => c.name === command).count++;
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
// Detect Skill tool (skills)
|
|
632
|
+
if (content.type === 'tool_use' && content.name === 'Skill' && content.input?.command) {
|
|
633
|
+
const skill = content.input.command;
|
|
634
|
+
if (!componentsUsed.skills.find(s => s.name === skill)) {
|
|
635
|
+
componentsUsed.skills.push({
|
|
636
|
+
name: skill,
|
|
637
|
+
count: 1
|
|
638
|
+
});
|
|
639
|
+
} else {
|
|
640
|
+
componentsUsed.skills.find(s => s.name === skill).count++;
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
});
|
|
644
|
+
}
|
|
645
|
+
});
|
|
646
|
+
|
|
647
|
+
// Format time durations
|
|
648
|
+
const formatDuration = (ms) => {
|
|
649
|
+
const hours = Math.floor(ms / (1000 * 60 * 60));
|
|
650
|
+
const minutes = Math.floor((ms % (1000 * 60 * 60)) / (1000 * 60));
|
|
651
|
+
const seconds = Math.floor((ms % (1000 * 60)) / 1000);
|
|
652
|
+
|
|
653
|
+
if (hours > 0) return `${hours}h ${minutes}m`;
|
|
654
|
+
if (minutes > 0) return `${minutes}m ${seconds}s`;
|
|
655
|
+
return `${seconds}s`;
|
|
656
|
+
};
|
|
657
|
+
|
|
658
|
+
// Generate optimization tips based on analytics
|
|
659
|
+
const optimizationTips = [];
|
|
660
|
+
|
|
661
|
+
if (cacheEfficiency < 20 && cacheTotal > 0) {
|
|
662
|
+
optimizationTips.push('• Low cache efficiency detected. Consider restructuring prompts to maximize cache reuse.');
|
|
663
|
+
}
|
|
664
|
+
if (conversation.toolUsage?.totalToolCalls > 50) {
|
|
665
|
+
optimizationTips.push('• High tool usage detected. Review if all tool calls are necessary.');
|
|
666
|
+
}
|
|
667
|
+
if (conversation.tokenUsage?.outputTokens > conversation.tokenUsage?.inputTokens * 2) {
|
|
668
|
+
optimizationTips.push('• Output tokens significantly exceed input. Consider more concise prompts.');
|
|
669
|
+
}
|
|
670
|
+
if (messages.length > 100) {
|
|
671
|
+
optimizationTips.push('• Long conversation detected. Consider starting fresh sessions for new topics to optimize context.');
|
|
672
|
+
}
|
|
673
|
+
if (conversation.modelInfo?.hasMultipleModels) {
|
|
674
|
+
optimizationTips.push('• Multiple models used in this session. Stick to one model for consistency.');
|
|
675
|
+
}
|
|
676
|
+
if (waitTimePercent > 70) {
|
|
677
|
+
optimizationTips.push('• High execution time detected. Consider breaking down complex tasks or optimizing tool usage.');
|
|
678
|
+
}
|
|
679
|
+
if (optimizationTips.length === 0) {
|
|
680
|
+
optimizationTips.push('• Great! Your conversation shows efficient usage patterns.');
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
// Prepare detailed analytics response
|
|
684
|
+
const analytics = {
|
|
685
|
+
// Overview
|
|
686
|
+
messageCount: messages.length,
|
|
687
|
+
totalTokens: conversation.tokenUsage?.total || 0,
|
|
688
|
+
toolCalls: conversation.toolUsage?.totalToolCalls || 0,
|
|
689
|
+
cacheEfficiency: `${cacheEfficiency}%`,
|
|
690
|
+
|
|
691
|
+
// Token breakdown
|
|
692
|
+
tokenUsage: {
|
|
693
|
+
inputTokens: conversation.tokenUsage?.inputTokens || 0,
|
|
694
|
+
outputTokens: conversation.tokenUsage?.outputTokens || 0,
|
|
695
|
+
cacheCreationTokens: conversation.tokenUsage?.cacheCreationTokens || 0,
|
|
696
|
+
cacheReadTokens: conversation.tokenUsage?.cacheReadTokens || 0,
|
|
697
|
+
total: conversation.tokenUsage?.total || 0
|
|
698
|
+
},
|
|
699
|
+
|
|
700
|
+
// Cost estimate
|
|
701
|
+
costEstimate: {
|
|
702
|
+
total: totalCost.toFixed(4),
|
|
703
|
+
breakdown: {
|
|
704
|
+
input: inputCost.toFixed(4),
|
|
705
|
+
output: outputCost.toFixed(4),
|
|
706
|
+
cacheWrite: cacheWriteCost.toFixed(4),
|
|
707
|
+
cacheRead: cacheReadCost.toFixed(4)
|
|
708
|
+
}
|
|
709
|
+
},
|
|
710
|
+
|
|
711
|
+
// Model info with usage percentages
|
|
712
|
+
modelInfo: {
|
|
713
|
+
primaryModel: conversation.modelInfo?.primaryModel || 'Unknown',
|
|
714
|
+
serviceTier: conversation.modelInfo?.currentServiceTier || 'Unknown',
|
|
715
|
+
hasMultipleModels: conversation.modelInfo?.hasMultipleModels || false,
|
|
716
|
+
allModels: conversation.modelInfo?.models || [],
|
|
717
|
+
modelUsage: (() => {
|
|
718
|
+
// Calculate model usage percentages
|
|
719
|
+
const modelCounts = {};
|
|
720
|
+
let totalMessages = 0;
|
|
721
|
+
|
|
722
|
+
messages.forEach(msg => {
|
|
723
|
+
if (msg.model && msg.model !== '<synthetic>') {
|
|
724
|
+
modelCounts[msg.model] = (modelCounts[msg.model] || 0) + 1;
|
|
725
|
+
totalMessages++;
|
|
726
|
+
}
|
|
727
|
+
});
|
|
728
|
+
|
|
729
|
+
return Object.entries(modelCounts).map(([model, count]) => ({
|
|
730
|
+
model,
|
|
731
|
+
count,
|
|
732
|
+
percentage: totalMessages > 0 ? ((count / totalMessages) * 100).toFixed(1) : '0.0'
|
|
733
|
+
})).sort((a, b) => b.count - a.count);
|
|
734
|
+
})()
|
|
735
|
+
},
|
|
736
|
+
|
|
737
|
+
// Tool usage
|
|
738
|
+
toolUsage: {
|
|
739
|
+
totalCalls: conversation.toolUsage?.totalToolCalls || 0,
|
|
740
|
+
uniqueTools: conversation.toolUsage?.uniqueTools || 0,
|
|
741
|
+
breakdown: conversation.toolUsage?.toolStats || {},
|
|
742
|
+
timeline: conversation.toolUsage?.toolTimeline || []
|
|
743
|
+
},
|
|
744
|
+
|
|
745
|
+
// Session timeline
|
|
746
|
+
timeline: {
|
|
747
|
+
startTime: startTime ? startTime.toISOString() : null,
|
|
748
|
+
endTime: endTime ? endTime.toISOString() : null,
|
|
749
|
+
duration: durationHours > 0
|
|
750
|
+
? `${durationHours}h ${durationMinutes}m`
|
|
751
|
+
: `${durationMinutes}m`,
|
|
752
|
+
durationMs: durationMs,
|
|
753
|
+
status: conversation.status || 'unknown'
|
|
754
|
+
},
|
|
755
|
+
|
|
756
|
+
// Time breakdown (conversing vs executing)
|
|
757
|
+
timeBreakdown: {
|
|
758
|
+
totalWaitTime: formatDuration(totalWaitTime),
|
|
759
|
+
totalUserTime: formatDuration(totalUserTime),
|
|
760
|
+
waitTimePercent: waitTimePercent,
|
|
761
|
+
userTimePercent: userTimePercent,
|
|
762
|
+
waitTimeMs: totalWaitTime,
|
|
763
|
+
userTimeMs: totalUserTime,
|
|
764
|
+
totalIterationTime: formatDuration(totalIterationTime)
|
|
765
|
+
},
|
|
766
|
+
|
|
767
|
+
// Components used (agents, commands, skills)
|
|
768
|
+
componentsUsed: {
|
|
769
|
+
agents: componentsUsed.agents.sort((a, b) => b.count - a.count),
|
|
770
|
+
slashCommands: componentsUsed.slashCommands.sort((a, b) => b.count - a.count),
|
|
771
|
+
skills: componentsUsed.skills.sort((a, b) => b.count - a.count),
|
|
772
|
+
totalAgents: componentsUsed.agents.length,
|
|
773
|
+
totalCommands: componentsUsed.slashCommands.length,
|
|
774
|
+
totalSkills: componentsUsed.skills.length
|
|
775
|
+
},
|
|
776
|
+
|
|
777
|
+
// Optimization tips
|
|
778
|
+
optimizationTips: optimizationTips,
|
|
779
|
+
|
|
780
|
+
// Metadata
|
|
781
|
+
conversationId: conversationId,
|
|
782
|
+
project: conversation.project || 'Unknown',
|
|
783
|
+
timestamp: new Date().toISOString()
|
|
784
|
+
};
|
|
785
|
+
|
|
786
|
+
res.json({
|
|
787
|
+
success: true,
|
|
788
|
+
analytics: analytics
|
|
789
|
+
});
|
|
790
|
+
} catch (error) {
|
|
791
|
+
console.error('Error fetching conversation analytics:', error);
|
|
792
|
+
res.status(500).json({
|
|
793
|
+
error: 'Failed to fetch analytics',
|
|
794
|
+
message: error.message
|
|
795
|
+
});
|
|
796
|
+
}
|
|
797
|
+
});
|
|
798
|
+
|
|
515
799
|
// Serve the mobile chats page as default
|
|
516
800
|
this.app.get('/', (req, res) => {
|
|
517
801
|
res.sendFile(path.join(__dirname, 'analytics-web', 'chats_mobile.html'));
|