claude-code-templates 1.12.2 → 1.13.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -188,13 +188,9 @@ class DashboardPage {
188
188
  <div class="metrics-cards-container">
189
189
  <!-- Conversations Card -->
190
190
  <div class="metric-card">
191
- <div class="metric-header">
192
- <div class="metric-icon">💬</div>
193
- <div class="metric-title">Conversations</div>
194
- </div>
195
191
  <div class="metric-primary">
196
192
  <span class="metric-primary-value" id="totalConversations">0</span>
197
- <span class="metric-primary-label">Total</span>
193
+ <span class="metric-primary-label">Total Conversations</span>
198
194
  </div>
199
195
  <div class="metric-secondary">
200
196
  <div class="metric-secondary-item">
@@ -214,13 +210,9 @@ class DashboardPage {
214
210
 
215
211
  <!-- Sessions Card -->
216
212
  <div class="metric-card">
217
- <div class="metric-header">
218
- <div class="metric-icon">⚡</div>
219
- <div class="metric-title">Sessions</div>
220
- </div>
221
213
  <div class="metric-primary">
222
214
  <span class="metric-primary-value" id="claudeSessions">0</span>
223
- <span class="metric-primary-label">Total</span>
215
+ <span class="metric-primary-label">Total Sessions</span>
224
216
  </div>
225
217
  <div class="metric-secondary">
226
218
  <div class="metric-secondary-item">
@@ -240,13 +232,9 @@ class DashboardPage {
240
232
 
241
233
  <!-- Tokens Card -->
242
234
  <div class="metric-card">
243
- <div class="metric-header">
244
- <div class="metric-icon">🔢</div>
245
- <div class="metric-title">Tokens</div>
246
- </div>
247
235
  <div class="metric-primary">
248
236
  <span class="metric-primary-value" id="totalTokens">0</span>
249
- <span class="metric-primary-label">Total</span>
237
+ <span class="metric-primary-label">Total Tokens</span>
250
238
  </div>
251
239
  <div class="metric-secondary">
252
240
  <div class="metric-secondary-item">
@@ -263,6 +251,28 @@ class DashboardPage {
263
251
  </div>
264
252
  </div>
265
253
  </div>
254
+
255
+ <!-- Agents Card -->
256
+ <div class="metric-card">
257
+ <div class="metric-primary">
258
+ <span class="metric-primary-value" id="totalAgentInvocations">0</span>
259
+ <span class="metric-primary-label">Total Agent Uses</span>
260
+ </div>
261
+ <div class="metric-secondary">
262
+ <div class="metric-secondary-item">
263
+ <span class="metric-secondary-label">Types:</span>
264
+ <span class="metric-secondary-value" id="totalAgentTypes">0</span>
265
+ </div>
266
+ <div class="metric-secondary-item">
267
+ <span class="metric-secondary-label">Top Agent:</span>
268
+ <span class="metric-secondary-value" id="topAgentName">None</span>
269
+ </div>
270
+ <div class="metric-secondary-item">
271
+ <span class="metric-secondary-label">Adoption:</span>
272
+ <span class="metric-secondary-value" id="agentAdoption">0%</span>
273
+ </div>
274
+ </div>
275
+ </div>
266
276
  </div>
267
277
 
268
278
  <!-- Session Timer Section -->
@@ -286,35 +296,96 @@ class DashboardPage {
286
296
  </div>
287
297
  </div>
288
298
 
289
- <!-- Charts Container (2x2 Grid) -->
299
+ <!-- Charts Container - Organized by Sections -->
290
300
  <div class="charts-container">
291
- <div class="chart-card">
292
- <div class="chart-title">
293
- 📊 token usage over time
294
- </div>
295
- <canvas id="tokenChart" class="chart-canvas"></canvas>
296
- </div>
297
301
 
298
- <div class="chart-card">
299
- <div class="chart-title">
300
- 🎯 project activity distribution
302
+ <!-- SECTION 1: Token Analytics -->
303
+ <div class="chart-section">
304
+ <div class="section-header">
305
+ <h3 class="section-title">🔢 Token Analytics</h3>
306
+ <p class="section-description">Monitor token consumption patterns and efficiency</p>
307
+ </div>
308
+ <div class="section-charts">
309
+ <div class="chart-card">
310
+ <div class="chart-title">
311
+ Token Usage Over Time
312
+ </div>
313
+ <canvas id="tokenChart" class="chart-canvas"></canvas>
314
+ </div>
315
+
316
+ <div class="chart-card">
317
+ <div class="chart-title">
318
+ Token Distribution by Type
319
+ </div>
320
+ <canvas id="tokenTypeChart" class="chart-canvas"></canvas>
321
+ </div>
322
+
323
+ <div class="chart-card">
324
+ <div class="chart-title">
325
+ Token Usage Over Time
326
+ </div>
327
+ <canvas id="tokenTimelineChart" class="chart-canvas"></canvas>
328
+ </div>
301
329
  </div>
302
- <canvas id="projectChart" class="chart-canvas"></canvas>
303
330
  </div>
304
-
305
- <div class="chart-card">
306
- <div class="chart-title">
307
- 🛠️ tool usage trends
331
+
332
+ <!-- SECTION 2: Workflow Intelligence -->
333
+ <div class="chart-section">
334
+ <div class="section-header">
335
+ <h3 class="section-title">🤖 Workflow Intelligence</h3>
336
+ <p class="section-description">Analyze agent usage and automation patterns</p>
337
+ </div>
338
+ <div class="section-charts">
339
+ <div class="chart-card">
340
+ <div class="chart-title">
341
+ Agent Usage Distribution
342
+ </div>
343
+ <canvas id="agentUsageChart" class="chart-canvas"></canvas>
344
+ </div>
345
+
346
+ <div class="chart-card">
347
+ <div class="chart-title">
348
+ Agent Activity Timeline
349
+ </div>
350
+ <canvas id="agentTimelineChart" class="chart-canvas"></canvas>
351
+ </div>
352
+
353
+ <div class="chart-card">
354
+ <div class="chart-title">
355
+ Workflow Efficiency Score
356
+ </div>
357
+ <canvas id="workflowEfficiencyChart" class="chart-canvas"></canvas>
358
+ </div>
308
359
  </div>
309
- <canvas id="toolChart" class="chart-canvas"></canvas>
310
360
  </div>
311
-
312
- <div class="chart-card">
313
- <div class="chart-title">
314
- tool activity summary
361
+
362
+ <!-- SECTION 3: Productivity Analytics -->
363
+ <div class="chart-section">
364
+ <div class="section-header">
365
+ <h3 class="section-title">📈 Productivity Analytics</h3>
366
+ <p class="section-description">Track project activity and tool utilization</p>
315
367
  </div>
316
- <div id="toolSummary" class="tool-summary">
317
- <!-- Tool summary will be loaded here -->
368
+ <div class="section-charts">
369
+ <div class="chart-card">
370
+ <div class="chart-title">
371
+ Project Activity Distribution
372
+ </div>
373
+ <canvas id="projectChart" class="chart-canvas"></canvas>
374
+ </div>
375
+
376
+ <div class="chart-card">
377
+ <div class="chart-title">
378
+ Tool Usage Patterns
379
+ </div>
380
+ <canvas id="toolChart" class="chart-canvas"></canvas>
381
+ </div>
382
+
383
+ <div class="chart-card">
384
+ <div class="chart-title">
385
+ Daily Productivity Trends
386
+ </div>
387
+ <canvas id="productivityChart" class="chart-canvas"></canvas>
388
+ </div>
318
389
  </div>
319
390
  </div>
320
391
  </div>
@@ -825,15 +896,19 @@ class DashboardPage {
825
896
  */
826
897
  async loadInitialData() {
827
898
  try {
828
- const [conversationsData, statesData] = await Promise.all([
899
+ const [conversationsData, statesData, agentData] = await Promise.all([
829
900
  this.dataService.getConversations(),
830
- this.dataService.getConversationStates()
901
+ this.dataService.getConversationStates(),
902
+ this.dataService.cachedFetch('/api/agents')
831
903
  ]);
832
904
 
833
905
  this.stateService.updateConversations(conversationsData.conversations);
834
906
  this.stateService.updateSummary(conversationsData.summary);
835
907
  this.stateService.updateConversationStates(statesData);
836
908
 
909
+ // Store agent data for charts
910
+ this.agentData = agentData;
911
+
837
912
  // Update dashboard with original format
838
913
  this.updateSummaryDisplay(
839
914
  conversationsData.summary,
@@ -843,6 +918,7 @@ class DashboardPage {
843
918
 
844
919
  this.updateLastUpdateTime();
845
920
  this.updateChartData(conversationsData);
921
+ this.updateAgentCharts(agentData);
846
922
  } catch (error) {
847
923
  console.error('Error loading initial data:', error);
848
924
 
@@ -948,6 +1024,11 @@ class DashboardPage {
948
1024
  this.updateTokenBreakdown(detailedTokenUsage);
949
1025
  }
950
1026
 
1027
+ // Update agent metrics if available
1028
+ if (this.agentData) {
1029
+ this.updateAgentMetrics(this.agentData);
1030
+ }
1031
+
951
1032
  // Store data for chart updates
952
1033
  this.allData = allData;
953
1034
  }
@@ -985,6 +1066,44 @@ class DashboardPage {
985
1066
  if (cacheTokens) cacheTokens.textContent = totalCache.toLocaleString();
986
1067
  }
987
1068
 
1069
+ /**
1070
+ * Update agent metrics in the agents card
1071
+ * @param {Object} agentData - Agent analytics data
1072
+ */
1073
+ updateAgentMetrics(agentData) {
1074
+ if (!agentData) return;
1075
+
1076
+ const totalAgentInvocations = this.container.querySelector('#totalAgentInvocations');
1077
+ const totalAgentTypes = this.container.querySelector('#totalAgentTypes');
1078
+ const topAgentName = this.container.querySelector('#topAgentName');
1079
+ const agentAdoption = this.container.querySelector('#agentAdoption');
1080
+
1081
+ // Update primary metric - total invocations
1082
+ if (totalAgentInvocations) {
1083
+ totalAgentInvocations.textContent = agentData.totalAgentInvocations?.toLocaleString() || '0';
1084
+ }
1085
+
1086
+ // Update secondary metrics
1087
+ if (totalAgentTypes) {
1088
+ totalAgentTypes.textContent = agentData.totalAgentTypes?.toLocaleString() || '0';
1089
+ }
1090
+
1091
+ if (topAgentName) {
1092
+ const topAgent = agentData.agentStats?.[0];
1093
+ if (topAgent) {
1094
+ topAgentName.textContent = topAgent.name;
1095
+ topAgentName.title = `${topAgent.totalInvocations} uses`;
1096
+ } else {
1097
+ topAgentName.textContent = 'None';
1098
+ }
1099
+ }
1100
+
1101
+ if (agentAdoption) {
1102
+ const adoptionRate = agentData.efficiency?.adoptionRate || '0';
1103
+ agentAdoption.textContent = adoptionRate + '%';
1104
+ }
1105
+ }
1106
+
988
1107
  /**
989
1108
  * Show token popover
990
1109
  */
@@ -1050,6 +1169,9 @@ class DashboardPage {
1050
1169
  if (this.allData) {
1051
1170
  this.updateChartData(this.allData);
1052
1171
  }
1172
+ if (this.agentData) {
1173
+ this.updateAgentCharts(this.agentData);
1174
+ }
1053
1175
  }
1054
1176
 
1055
1177
  /**
@@ -1095,9 +1217,17 @@ class DashboardPage {
1095
1217
  updateChartData(data) {
1096
1218
  if (!data || !data.conversations) return;
1097
1219
 
1220
+ // Token Analytics Section
1098
1221
  this.updateTokenChart(data.conversations);
1222
+ this.updateTokenTypeChart(data);
1223
+ this.updateTokenTimelineChart(data);
1224
+
1225
+ // Productivity Analytics Section
1099
1226
  this.updateProjectChart(data.conversations);
1100
1227
  this.updateToolChart(data.conversations);
1228
+ this.updateProductivityChart(data);
1229
+
1230
+ // Legacy tool summary (keeping for now)
1101
1231
  this.updateToolSummary(data.conversations);
1102
1232
  }
1103
1233
 
@@ -1256,6 +1386,647 @@ class DashboardPage {
1256
1386
  `;
1257
1387
  }
1258
1388
 
1389
+ /**
1390
+ * Update agent usage charts
1391
+ * @param {Object} agentData - Agent analytics data
1392
+ */
1393
+ updateAgentCharts(agentData) {
1394
+ if (!agentData || !agentData.agentStats) {
1395
+ console.warn('No agent data available for charts');
1396
+ return;
1397
+ }
1398
+
1399
+ this.updateAgentUsageChart(agentData);
1400
+ this.updateAgentTimelineChart(agentData);
1401
+ this.updateWorkflowEfficiencyChart(agentData);
1402
+ }
1403
+
1404
+ /**
1405
+ * Update agent usage distribution chart
1406
+ * @param {Object} agentData - Agent analytics data
1407
+ */
1408
+ updateAgentUsageChart(agentData) {
1409
+ const canvas = this.container.querySelector('#agentUsageChart');
1410
+ if (!canvas) {
1411
+ console.warn('Agent usage chart canvas not found');
1412
+ return;
1413
+ }
1414
+
1415
+ // Destroy existing chart if it exists
1416
+ const existingChart = Chart.getChart(canvas);
1417
+ if (existingChart) {
1418
+ existingChart.destroy();
1419
+ }
1420
+
1421
+ const ctx = canvas.getContext('2d');
1422
+ const agentStats = agentData.agentStats || [];
1423
+
1424
+ if (agentStats.length === 0) {
1425
+ // Show "no data" message
1426
+ ctx.fillStyle = '#7d8590';
1427
+ ctx.textAlign = 'center';
1428
+ ctx.font = '14px Monaco, monospace';
1429
+ ctx.fillText('No agent usage data', canvas.width / 2, canvas.height / 2);
1430
+ return;
1431
+ }
1432
+
1433
+ new Chart(ctx, {
1434
+ type: 'doughnut',
1435
+ data: {
1436
+ labels: agentStats.map(agent => agent.name),
1437
+ datasets: [{
1438
+ data: agentStats.map(agent => agent.totalInvocations),
1439
+ backgroundColor: agentStats.map(agent => agent.color),
1440
+ borderColor: '#0d1117',
1441
+ borderWidth: 2,
1442
+ hoverBorderWidth: 3
1443
+ }]
1444
+ },
1445
+ options: {
1446
+ responsive: true,
1447
+ maintainAspectRatio: false,
1448
+ plugins: {
1449
+ legend: {
1450
+ position: 'bottom',
1451
+ labels: {
1452
+ color: '#c9d1d9',
1453
+ padding: 10,
1454
+ usePointStyle: true,
1455
+ font: {
1456
+ family: "'Monaco', 'Menlo', 'Ubuntu Mono', monospace",
1457
+ size: 11
1458
+ }
1459
+ }
1460
+ },
1461
+ tooltip: {
1462
+ titleFont: {
1463
+ family: "'Monaco', 'Menlo', 'Ubuntu Mono', monospace"
1464
+ },
1465
+ bodyFont: {
1466
+ family: "'Monaco', 'Menlo', 'Ubuntu Mono', monospace"
1467
+ },
1468
+ callbacks: {
1469
+ label: function(context) {
1470
+ const agent = agentStats[context.dataIndex];
1471
+ return `${agent.name}: ${context.parsed} uses (${agent.uniqueConversations} conversations)`;
1472
+ }
1473
+ }
1474
+ }
1475
+ },
1476
+ cutout: '60%'
1477
+ }
1478
+ });
1479
+ }
1480
+
1481
+ /**
1482
+ * Update agent usage timeline chart
1483
+ * @param {Object} agentData - Agent analytics data
1484
+ */
1485
+ updateAgentTimelineChart(agentData) {
1486
+ const canvas = this.container.querySelector('#agentTimelineChart');
1487
+ if (!canvas) {
1488
+ console.warn('Agent timeline chart canvas not found');
1489
+ return;
1490
+ }
1491
+
1492
+ // Destroy existing chart if it exists
1493
+ const existingChart = Chart.getChart(canvas);
1494
+ if (existingChart) {
1495
+ existingChart.destroy();
1496
+ }
1497
+
1498
+ const ctx = canvas.getContext('2d');
1499
+ const usageByDay = agentData.usageByDay || [];
1500
+
1501
+ if (usageByDay.length === 0) {
1502
+ // Show "no data" message
1503
+ ctx.fillStyle = '#7d8590';
1504
+ ctx.textAlign = 'center';
1505
+ ctx.font = '14px Monaco, monospace';
1506
+ ctx.fillText('No timeline data', canvas.width / 2, canvas.height / 2);
1507
+ return;
1508
+ }
1509
+
1510
+ new Chart(ctx, {
1511
+ type: 'line',
1512
+ data: {
1513
+ labels: usageByDay.map(d => new Date(d.date).toLocaleDateString()),
1514
+ datasets: [{
1515
+ label: 'Agent Usage',
1516
+ data: usageByDay.map(d => d.count),
1517
+ borderColor: '#3fb950',
1518
+ backgroundColor: 'rgba(63, 185, 80, 0.1)',
1519
+ borderWidth: 2,
1520
+ fill: true,
1521
+ tension: 0.3,
1522
+ pointBackgroundColor: '#3fb950',
1523
+ pointBorderColor: '#ffffff',
1524
+ pointBorderWidth: 2,
1525
+ pointRadius: 4,
1526
+ pointHoverRadius: 6
1527
+ }]
1528
+ },
1529
+ options: {
1530
+ responsive: true,
1531
+ maintainAspectRatio: false,
1532
+ plugins: {
1533
+ legend: {
1534
+ labels: {
1535
+ color: '#c9d1d9',
1536
+ font: {
1537
+ family: "'Monaco', 'Menlo', 'Ubuntu Mono', monospace",
1538
+ size: 11
1539
+ }
1540
+ }
1541
+ },
1542
+ tooltip: {
1543
+ titleFont: {
1544
+ family: "'Monaco', 'Menlo', 'Ubuntu Mono', monospace"
1545
+ },
1546
+ bodyFont: {
1547
+ family: "'Monaco', 'Menlo', 'Ubuntu Mono', monospace"
1548
+ },
1549
+ callbacks: {
1550
+ label: function(context) {
1551
+ return `Agent invocations: ${context.parsed.y}`;
1552
+ }
1553
+ }
1554
+ }
1555
+ },
1556
+ scales: {
1557
+ x: {
1558
+ ticks: {
1559
+ color: '#7d8590',
1560
+ font: {
1561
+ family: "'Monaco', 'Menlo', 'Ubuntu Mono', monospace"
1562
+ }
1563
+ },
1564
+ grid: {
1565
+ color: '#30363d'
1566
+ }
1567
+ },
1568
+ y: {
1569
+ beginAtZero: true,
1570
+ ticks: {
1571
+ color: '#7d8590',
1572
+ font: {
1573
+ family: "'Monaco', 'Menlo', 'Ubuntu Mono', monospace"
1574
+ },
1575
+ stepSize: 1
1576
+ },
1577
+ grid: {
1578
+ color: '#30363d'
1579
+ }
1580
+ }
1581
+ }
1582
+ }
1583
+ });
1584
+ }
1585
+
1586
+ /**
1587
+ * Update workflow efficiency chart
1588
+ * @param {Object} agentData - Agent analytics data
1589
+ */
1590
+ updateWorkflowEfficiencyChart(agentData) {
1591
+ const canvas = this.container.querySelector('#workflowEfficiencyChart');
1592
+ if (!canvas) {
1593
+ console.warn('Workflow efficiency chart canvas not found');
1594
+ return;
1595
+ }
1596
+
1597
+ // Destroy existing chart if it exists
1598
+ const existingChart = Chart.getChart(canvas);
1599
+ if (existingChart) {
1600
+ existingChart.destroy();
1601
+ }
1602
+
1603
+ const ctx = canvas.getContext('2d');
1604
+ const efficiency = agentData.efficiency || {};
1605
+
1606
+ const data = {
1607
+ labels: ['Adoption Rate', 'Workflow Completion', 'Time Efficiency', 'Success Rate'],
1608
+ datasets: [{
1609
+ label: 'Efficiency %',
1610
+ data: [
1611
+ efficiency.adoptionRate || 0,
1612
+ efficiency.workflowCompletion || 0,
1613
+ efficiency.timeEfficiency || 0,
1614
+ efficiency.successRate || 0
1615
+ ],
1616
+ backgroundColor: [
1617
+ 'rgba(63, 185, 80, 0.8)',
1618
+ 'rgba(88, 166, 255, 0.8)',
1619
+ 'rgba(249, 115, 22, 0.8)',
1620
+ 'rgba(213, 116, 85, 0.8)'
1621
+ ],
1622
+ borderColor: [
1623
+ '#3fb950',
1624
+ '#58a6ff',
1625
+ '#f97316',
1626
+ '#d57455'
1627
+ ],
1628
+ borderWidth: 2
1629
+ }]
1630
+ };
1631
+
1632
+ new Chart(ctx, {
1633
+ type: 'radar',
1634
+ data: data,
1635
+ options: {
1636
+ responsive: true,
1637
+ maintainAspectRatio: false,
1638
+ plugins: {
1639
+ legend: {
1640
+ display: false
1641
+ },
1642
+ tooltip: {
1643
+ callbacks: {
1644
+ label: function(context) {
1645
+ const label = context.label;
1646
+ const value = context.parsed.r;
1647
+ return `${label}: ${value.toFixed(1)}%`;
1648
+ }
1649
+ }
1650
+ }
1651
+ },
1652
+ scales: {
1653
+ r: {
1654
+ beginAtZero: true,
1655
+ max: 100,
1656
+ ticks: {
1657
+ stepSize: 20,
1658
+ color: '#7d8590',
1659
+ backdropColor: 'transparent'
1660
+ },
1661
+ grid: {
1662
+ color: '#30363d'
1663
+ },
1664
+ angleLines: {
1665
+ color: '#30363d'
1666
+ },
1667
+ pointLabels: {
1668
+ color: '#c9d1d9',
1669
+ font: {
1670
+ size: 11
1671
+ }
1672
+ }
1673
+ }
1674
+ }
1675
+ }
1676
+ });
1677
+ }
1678
+
1679
+ /**
1680
+ * Update token type distribution chart
1681
+ * @param {Object} data - Chart data
1682
+ */
1683
+ updateTokenTypeChart(data) {
1684
+ const canvas = this.container.querySelector('#tokenTypeChart');
1685
+ if (!canvas) {
1686
+ console.warn('Token type chart canvas not found');
1687
+ return;
1688
+ }
1689
+
1690
+ const existingChart = Chart.getChart(canvas);
1691
+ if (existingChart) existingChart.destroy();
1692
+
1693
+ const ctx = canvas.getContext('2d');
1694
+ const tokenData = data.detailedTokenUsage || {};
1695
+
1696
+ console.log('Token type chart data:', tokenData);
1697
+
1698
+ const chartData = [
1699
+ tokenData.inputTokens || 0,
1700
+ tokenData.outputTokens || 0,
1701
+ tokenData.cacheCreationTokens || 0,
1702
+ tokenData.cacheReadTokens || 0
1703
+ ];
1704
+
1705
+ const totalTokens = chartData.reduce((sum, val) => sum + val, 0);
1706
+
1707
+ if (totalTokens === 0) {
1708
+ // Show "no data" message
1709
+ ctx.fillStyle = '#7d8590';
1710
+ ctx.textAlign = 'center';
1711
+ ctx.font = '14px Monaco, monospace';
1712
+ ctx.fillText('No token data available', canvas.width / 2, canvas.height / 2);
1713
+ return;
1714
+ }
1715
+
1716
+ new Chart(ctx, {
1717
+ type: 'doughnut',
1718
+ data: {
1719
+ labels: ['Input Tokens', 'Output Tokens', 'Cache Creation', 'Cache Read'],
1720
+ datasets: [{
1721
+ data: chartData,
1722
+ backgroundColor: ['#3fb950', '#58a6ff', '#f97316', '#d57455'],
1723
+ borderColor: '#0d1117',
1724
+ borderWidth: 2
1725
+ }]
1726
+ },
1727
+ options: {
1728
+ responsive: true,
1729
+ maintainAspectRatio: false,
1730
+ plugins: {
1731
+ legend: {
1732
+ position: 'bottom',
1733
+ labels: {
1734
+ color: '#c9d1d9',
1735
+ font: { size: 11 }
1736
+ }
1737
+ },
1738
+ tooltip: {
1739
+ callbacks: {
1740
+ label: function(context) {
1741
+ const label = context.label;
1742
+ const value = context.parsed;
1743
+ const total = context.dataset.data.reduce((sum, val) => sum + val, 0);
1744
+ const percentage = total > 0 ? ((value / total) * 100).toFixed(1) : 0;
1745
+ return `${label}: ${value.toLocaleString()} tokens (${percentage}%)`;
1746
+ }
1747
+ }
1748
+ }
1749
+ }
1750
+ }
1751
+ });
1752
+ }
1753
+
1754
+ /**
1755
+ * Update token usage over time chart
1756
+ * @param {Object} data - Chart data
1757
+ */
1758
+ updateTokenTimelineChart(data) {
1759
+ const canvas = this.container.querySelector('#tokenTimelineChart');
1760
+ if (!canvas) {
1761
+ console.warn('Token timeline chart canvas not found');
1762
+ return;
1763
+ }
1764
+
1765
+ const existingChart = Chart.getChart(canvas);
1766
+ if (existingChart) existingChart.destroy();
1767
+
1768
+ const ctx = canvas.getContext('2d');
1769
+ const conversations = data.conversations || [];
1770
+
1771
+ if (conversations.length === 0) {
1772
+ // Show "no data" message
1773
+ ctx.fillStyle = '#7d8590';
1774
+ ctx.textAlign = 'center';
1775
+ ctx.font = '14px Monaco, monospace';
1776
+ ctx.fillText('No token timeline data', canvas.width / 2, canvas.height / 2);
1777
+ return;
1778
+ }
1779
+
1780
+ // Calculate daily token usage
1781
+ const dailyTokens = this.calculateDailyTokenUsage(conversations);
1782
+
1783
+ new Chart(ctx, {
1784
+ type: 'line',
1785
+ data: {
1786
+ labels: dailyTokens.labels,
1787
+ datasets: [{
1788
+ label: 'Input Tokens',
1789
+ data: dailyTokens.inputTokens,
1790
+ borderColor: '#3fb950',
1791
+ backgroundColor: 'rgba(63, 185, 80, 0.1)',
1792
+ fill: false,
1793
+ tension: 0.3,
1794
+ pointBackgroundColor: '#3fb950',
1795
+ pointBorderColor: '#ffffff',
1796
+ pointBorderWidth: 2,
1797
+ pointRadius: 3,
1798
+ pointHoverRadius: 5
1799
+ }, {
1800
+ label: 'Output Tokens',
1801
+ data: dailyTokens.outputTokens,
1802
+ borderColor: '#58a6ff',
1803
+ backgroundColor: 'rgba(88, 166, 255, 0.1)',
1804
+ fill: false,
1805
+ tension: 0.3,
1806
+ pointBackgroundColor: '#58a6ff',
1807
+ pointBorderColor: '#ffffff',
1808
+ pointBorderWidth: 2,
1809
+ pointRadius: 3,
1810
+ pointHoverRadius: 5
1811
+ }, {
1812
+ label: 'Cache Usage',
1813
+ data: dailyTokens.cacheTokens,
1814
+ borderColor: '#f97316',
1815
+ backgroundColor: 'rgba(249, 115, 22, 0.1)',
1816
+ fill: false,
1817
+ tension: 0.3,
1818
+ pointBackgroundColor: '#f97316',
1819
+ pointBorderColor: '#ffffff',
1820
+ pointBorderWidth: 2,
1821
+ pointRadius: 3,
1822
+ pointHoverRadius: 5
1823
+ }]
1824
+ },
1825
+ options: {
1826
+ responsive: true,
1827
+ maintainAspectRatio: false,
1828
+ plugins: {
1829
+ legend: {
1830
+ position: 'bottom',
1831
+ labels: {
1832
+ color: '#c9d1d9',
1833
+ font: { size: 11 },
1834
+ padding: 15,
1835
+ usePointStyle: true
1836
+ }
1837
+ },
1838
+ tooltip: {
1839
+ callbacks: {
1840
+ label: function(context) {
1841
+ const label = context.dataset.label;
1842
+ const value = context.parsed.y;
1843
+ return `${label}: ${value.toLocaleString()} tokens`;
1844
+ }
1845
+ }
1846
+ }
1847
+ },
1848
+ scales: {
1849
+ x: {
1850
+ ticks: { color: '#7d8590' },
1851
+ grid: { color: '#30363d' }
1852
+ },
1853
+ y: {
1854
+ beginAtZero: true,
1855
+ ticks: {
1856
+ color: '#7d8590',
1857
+ callback: function(value) {
1858
+ return value.toLocaleString();
1859
+ }
1860
+ },
1861
+ grid: { color: '#30363d' }
1862
+ }
1863
+ },
1864
+ interaction: {
1865
+ intersect: false,
1866
+ mode: 'index'
1867
+ }
1868
+ }
1869
+ });
1870
+ }
1871
+
1872
+ /**
1873
+ * Update daily productivity trends chart
1874
+ * @param {Object} data - Chart data
1875
+ */
1876
+ updateProductivityChart(data) {
1877
+ const canvas = this.container.querySelector('#productivityChart');
1878
+ if (!canvas) return;
1879
+
1880
+ const existingChart = Chart.getChart(canvas);
1881
+ if (existingChart) existingChart.destroy();
1882
+
1883
+ const ctx = canvas.getContext('2d');
1884
+
1885
+ // Calculate productivity metrics by day
1886
+ const dailyData = this.calculateDailyProductivity(data);
1887
+
1888
+ new Chart(ctx, {
1889
+ type: 'line',
1890
+ data: {
1891
+ labels: dailyData.labels,
1892
+ datasets: [{
1893
+ label: 'Messages per Day',
1894
+ data: dailyData.messages,
1895
+ borderColor: '#3fb950',
1896
+ backgroundColor: 'rgba(63, 185, 80, 0.1)',
1897
+ fill: true,
1898
+ tension: 0.3
1899
+ }, {
1900
+ label: 'Tokens per Day',
1901
+ data: dailyData.tokens,
1902
+ borderColor: '#58a6ff',
1903
+ backgroundColor: 'rgba(88, 166, 255, 0.1)',
1904
+ fill: true,
1905
+ tension: 0.3,
1906
+ yAxisID: 'y1'
1907
+ }]
1908
+ },
1909
+ options: {
1910
+ responsive: true,
1911
+ maintainAspectRatio: false,
1912
+ plugins: {
1913
+ legend: {
1914
+ labels: { color: '#c9d1d9', font: { size: 11 } }
1915
+ },
1916
+ tooltip: {
1917
+ callbacks: {
1918
+ label: function(context) {
1919
+ const label = context.dataset.label;
1920
+ const value = context.parsed.y;
1921
+ if (label === 'Messages per Day') {
1922
+ return `${label}: ${value} messages`;
1923
+ } else {
1924
+ return `${label}: ${value.toLocaleString()} tokens`;
1925
+ }
1926
+ }
1927
+ }
1928
+ }
1929
+ },
1930
+ scales: {
1931
+ x: {
1932
+ ticks: { color: '#7d8590' },
1933
+ grid: { color: '#30363d' }
1934
+ },
1935
+ y: {
1936
+ type: 'linear',
1937
+ display: true,
1938
+ position: 'left',
1939
+ ticks: { color: '#7d8590' },
1940
+ grid: { color: '#30363d' }
1941
+ },
1942
+ y1: {
1943
+ type: 'linear',
1944
+ display: true,
1945
+ position: 'right',
1946
+ ticks: { color: '#7d8590' },
1947
+ grid: { drawOnChartArea: false }
1948
+ }
1949
+ },
1950
+ interaction: {
1951
+ intersect: false,
1952
+ mode: 'index'
1953
+ }
1954
+ }
1955
+ });
1956
+ }
1957
+
1958
+ /**
1959
+ * Calculate daily productivity metrics
1960
+ * @param {Object} data - Raw data
1961
+ * @returns {Object} Processed daily data
1962
+ */
1963
+ /**
1964
+ * Calculate daily token usage from conversations
1965
+ * @param {Array} conversations - Array of conversation objects
1966
+ * @returns {Object} Daily token data
1967
+ */
1968
+ calculateDailyTokenUsage(conversations) {
1969
+ const dailyData = {};
1970
+ const { fromDate, toDate } = this.getDateRange();
1971
+
1972
+ conversations.forEach(conv => {
1973
+ const convDate = new Date(conv.lastModified);
1974
+ if (convDate >= fromDate && convDate <= toDate) {
1975
+ const dateKey = convDate.toISOString().split('T')[0]; // YYYY-MM-DD
1976
+
1977
+ if (!dailyData[dateKey]) {
1978
+ dailyData[dateKey] = {
1979
+ inputTokens: 0,
1980
+ outputTokens: 0,
1981
+ cacheTokens: 0
1982
+ };
1983
+ }
1984
+
1985
+ if (conv.tokenUsage) {
1986
+ dailyData[dateKey].inputTokens += conv.tokenUsage.inputTokens || 0;
1987
+ dailyData[dateKey].outputTokens += conv.tokenUsage.outputTokens || 0;
1988
+ dailyData[dateKey].cacheTokens += (conv.tokenUsage.cacheCreationTokens || 0) + (conv.tokenUsage.cacheReadTokens || 0);
1989
+ }
1990
+ }
1991
+ });
1992
+
1993
+ // Sort dates and create arrays
1994
+ const sortedDates = Object.keys(dailyData).sort();
1995
+ const labels = sortedDates.map(date => new Date(date).toLocaleDateString());
1996
+ const inputTokens = sortedDates.map(date => dailyData[date].inputTokens);
1997
+ const outputTokens = sortedDates.map(date => dailyData[date].outputTokens);
1998
+ const cacheTokens = sortedDates.map(date => dailyData[date].cacheTokens);
1999
+
2000
+ return { labels, inputTokens, outputTokens, cacheTokens };
2001
+ }
2002
+
2003
+ calculateDailyProductivity(data) {
2004
+ const conversations = data.conversations || [];
2005
+ const dailyStats = {};
2006
+
2007
+ // Group data by day
2008
+ conversations.forEach(conv => {
2009
+ if (!conv.lastModified) return;
2010
+
2011
+ const date = new Date(conv.lastModified).toDateString();
2012
+ if (!dailyStats[date]) {
2013
+ dailyStats[date] = { messages: 0, tokens: 0 };
2014
+ }
2015
+
2016
+ dailyStats[date].messages += conv.messageCount || 0;
2017
+ dailyStats[date].tokens += (conv.tokenUsage?.inputTokens || 0) + (conv.tokenUsage?.outputTokens || 0);
2018
+ });
2019
+
2020
+ // Convert to arrays for chart
2021
+ const sortedDates = Object.keys(dailyStats).sort((a, b) => new Date(a) - new Date(b));
2022
+
2023
+ return {
2024
+ labels: sortedDates.map(date => new Date(date).toLocaleDateString()),
2025
+ messages: sortedDates.map(date => dailyStats[date].messages),
2026
+ tokens: sortedDates.map(date => Math.round(dailyStats[date].tokens / 1000)) // Convert to K tokens
2027
+ };
2028
+ }
2029
+
1259
2030
  /**
1260
2031
  * Update usage chart
1261
2032
  * @param {string} period - Time period