claude-code-templates 1.12.1 → 1.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/analytics/core/AgentAnalyzer.js +341 -0
- package/src/analytics/core/SessionAnalyzer.js +101 -46
- package/src/analytics-web/components/AgentAnalytics.js +710 -0
- package/src/analytics-web/components/DashboardPage.js +810 -39
- package/src/analytics-web/components/SessionTimer.js +14 -11
- package/src/analytics-web/index.html +666 -65
- package/src/analytics.js +112 -10
- package/src/analytics-web/components/Dashboard.js.deprecated +0 -589
|
@@ -1,589 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Dashboard - Main dashboard component that orchestrates all other components
|
|
3
|
-
* Part of the modular frontend architecture
|
|
4
|
-
*/
|
|
5
|
-
class Dashboard {
|
|
6
|
-
constructor(container, services) {
|
|
7
|
-
this.container = container;
|
|
8
|
-
this.dataService = services.data;
|
|
9
|
-
this.stateService = services.state;
|
|
10
|
-
this.chartService = services.chart;
|
|
11
|
-
|
|
12
|
-
this.components = {};
|
|
13
|
-
this.refreshInterval = null;
|
|
14
|
-
this.isInitialized = false;
|
|
15
|
-
|
|
16
|
-
// Subscribe to state changes
|
|
17
|
-
this.unsubscribe = this.stateService.subscribe(this.handleStateChange.bind(this));
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* Initialize the dashboard
|
|
22
|
-
*/
|
|
23
|
-
async initialize() {
|
|
24
|
-
if (this.isInitialized) return;
|
|
25
|
-
|
|
26
|
-
try {
|
|
27
|
-
await this.render();
|
|
28
|
-
this.stateService.setLoading(true);
|
|
29
|
-
await this.initializeComponents();
|
|
30
|
-
await this.loadInitialData();
|
|
31
|
-
this.startPeriodicRefresh();
|
|
32
|
-
this.isInitialized = true;
|
|
33
|
-
} catch (error) {
|
|
34
|
-
console.error('Error initializing dashboard:', error);
|
|
35
|
-
this.stateService.setError(error);
|
|
36
|
-
} finally {
|
|
37
|
-
this.stateService.setLoading(false);
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* Handle state changes from StateService
|
|
43
|
-
* @param {Object} state - New state
|
|
44
|
-
* @param {string} action - Action that caused the change
|
|
45
|
-
*/
|
|
46
|
-
handleStateChange(state, action) {
|
|
47
|
-
switch (action) {
|
|
48
|
-
case 'update_conversations':
|
|
49
|
-
this.updateSummaryDisplay(state.summary);
|
|
50
|
-
break;
|
|
51
|
-
case 'update_conversation_states':
|
|
52
|
-
this.updateConversationStates(state.conversationStates);
|
|
53
|
-
break;
|
|
54
|
-
case 'set_loading':
|
|
55
|
-
this.updateLoadingState(state.isLoading);
|
|
56
|
-
break;
|
|
57
|
-
case 'set_error':
|
|
58
|
-
this.updateErrorState(state.error);
|
|
59
|
-
break;
|
|
60
|
-
case 'conversation_state_change':
|
|
61
|
-
this.handleConversationStateChange(state);
|
|
62
|
-
break;
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
/**
|
|
67
|
-
* Render the dashboard structure
|
|
68
|
-
*/
|
|
69
|
-
async render() {
|
|
70
|
-
this.container.innerHTML = `
|
|
71
|
-
<div class="dashboard-container">
|
|
72
|
-
<!-- Header -->
|
|
73
|
-
<div class="dashboard-header">
|
|
74
|
-
<div class="terminal-title">
|
|
75
|
-
<span class="status-dot"></span>
|
|
76
|
-
Claude Code Analytics - Terminal
|
|
77
|
-
</div>
|
|
78
|
-
<div class="header-controls">
|
|
79
|
-
<button class="control-btn" id="refresh-btn" title="Refresh data">
|
|
80
|
-
<span class="refresh-icon">🔄</span>
|
|
81
|
-
Refresh
|
|
82
|
-
</button>
|
|
83
|
-
<button class="control-btn" id="notifications-btn" title="Toggle notifications">
|
|
84
|
-
<span class="notification-icon">🔔</span>
|
|
85
|
-
Notifications
|
|
86
|
-
</button>
|
|
87
|
-
<div class="last-update">
|
|
88
|
-
<span id="last-update-text">Never</span>
|
|
89
|
-
</div>
|
|
90
|
-
</div>
|
|
91
|
-
</div>
|
|
92
|
-
|
|
93
|
-
<!-- Loading State -->
|
|
94
|
-
<div class="loading-overlay" id="loading-overlay" style="display: none;">
|
|
95
|
-
<div class="loading-spinner"></div>
|
|
96
|
-
<span class="loading-text">Loading dashboard...</span>
|
|
97
|
-
</div>
|
|
98
|
-
|
|
99
|
-
<!-- Error State -->
|
|
100
|
-
<div class="error-banner" id="error-banner" style="display: none;">
|
|
101
|
-
<span class="error-icon">⚠️</span>
|
|
102
|
-
<span class="error-text"></span>
|
|
103
|
-
<button class="error-close" id="error-close">×</button>
|
|
104
|
-
</div>
|
|
105
|
-
|
|
106
|
-
<!-- Summary Cards -->
|
|
107
|
-
<div class="summary-section">
|
|
108
|
-
<div class="summary-cards">
|
|
109
|
-
<div class="summary-card">
|
|
110
|
-
<div class="card-header">
|
|
111
|
-
<h3>Total Conversations</h3>
|
|
112
|
-
<span class="card-icon">💬</span>
|
|
113
|
-
</div>
|
|
114
|
-
<div class="card-value" id="total-conversations">0</div>
|
|
115
|
-
<div class="card-change">
|
|
116
|
-
<span class="change-text" id="conversations-change">+0 today</span>
|
|
117
|
-
</div>
|
|
118
|
-
</div>
|
|
119
|
-
|
|
120
|
-
<div class="summary-card">
|
|
121
|
-
<div class="card-header">
|
|
122
|
-
<h3>Active Sessions</h3>
|
|
123
|
-
<span class="card-icon">⚡</span>
|
|
124
|
-
</div>
|
|
125
|
-
<div class="card-value" id="active-sessions">0</div>
|
|
126
|
-
<div class="card-change">
|
|
127
|
-
<span class="change-text" id="sessions-change">0 running</span>
|
|
128
|
-
</div>
|
|
129
|
-
</div>
|
|
130
|
-
|
|
131
|
-
<div class="summary-card">
|
|
132
|
-
<div class="card-header">
|
|
133
|
-
<h3>Total Tokens</h3>
|
|
134
|
-
<span class="card-icon">🔤</span>
|
|
135
|
-
</div>
|
|
136
|
-
<div class="card-value" id="total-tokens">0</div>
|
|
137
|
-
<div class="card-change">
|
|
138
|
-
<span class="change-text" id="tokens-change">0 avg/conv</span>
|
|
139
|
-
</div>
|
|
140
|
-
</div>
|
|
141
|
-
|
|
142
|
-
<div class="summary-card">
|
|
143
|
-
<div class="card-header">
|
|
144
|
-
<h3>Projects</h3>
|
|
145
|
-
<span class="card-icon">📁</span>
|
|
146
|
-
</div>
|
|
147
|
-
<div class="card-value" id="total-projects">0</div>
|
|
148
|
-
<div class="card-change">
|
|
149
|
-
<span class="change-text" id="projects-change">0 active</span>
|
|
150
|
-
</div>
|
|
151
|
-
</div>
|
|
152
|
-
</div>
|
|
153
|
-
</div>
|
|
154
|
-
|
|
155
|
-
<!-- Session Timer Section -->
|
|
156
|
-
<div class="session-timer-section">
|
|
157
|
-
<div id="session-timer-container">
|
|
158
|
-
<!-- SessionTimer component will be mounted here -->
|
|
159
|
-
</div>
|
|
160
|
-
</div>
|
|
161
|
-
|
|
162
|
-
<!-- Charts Section -->
|
|
163
|
-
<div class="charts-section">
|
|
164
|
-
<div class="chart-container">
|
|
165
|
-
<div class="chart-header">
|
|
166
|
-
<h3>Usage Over Time</h3>
|
|
167
|
-
<div class="chart-controls">
|
|
168
|
-
<select id="chart-period">
|
|
169
|
-
<option value="7d">Last 7 days</option>
|
|
170
|
-
<option value="30d">Last 30 days</option>
|
|
171
|
-
<option value="90d">Last 90 days</option>
|
|
172
|
-
</select>
|
|
173
|
-
</div>
|
|
174
|
-
</div>
|
|
175
|
-
<div class="chart-canvas-container">
|
|
176
|
-
<canvas id="usage-chart"></canvas>
|
|
177
|
-
</div>
|
|
178
|
-
</div>
|
|
179
|
-
</div>
|
|
180
|
-
|
|
181
|
-
<!-- Conversations Table -->
|
|
182
|
-
<div class="conversations-section">
|
|
183
|
-
<div class="section-header">
|
|
184
|
-
<h3>Recent Conversations</h3>
|
|
185
|
-
<div class="section-controls">
|
|
186
|
-
<button class="control-btn" id="export-btn">
|
|
187
|
-
<span class="export-icon">📤</span>
|
|
188
|
-
Export
|
|
189
|
-
</button>
|
|
190
|
-
</div>
|
|
191
|
-
</div>
|
|
192
|
-
<div class="conversations-table-container" id="conversations-table">
|
|
193
|
-
<!-- ConversationTable component will be mounted here -->
|
|
194
|
-
</div>
|
|
195
|
-
</div>
|
|
196
|
-
|
|
197
|
-
<!-- Footer -->
|
|
198
|
-
<div class="dashboard-footer">
|
|
199
|
-
<div class="footer-info">
|
|
200
|
-
<span>Analytics Dashboard v1.0</span>
|
|
201
|
-
<span>•</span>
|
|
202
|
-
<span id="connection-status">Connected</span>
|
|
203
|
-
</div>
|
|
204
|
-
</div>
|
|
205
|
-
</div>
|
|
206
|
-
`;
|
|
207
|
-
|
|
208
|
-
this.bindEvents();
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
/**
|
|
212
|
-
* Initialize child components
|
|
213
|
-
*/
|
|
214
|
-
async initializeComponents() {
|
|
215
|
-
// Initialize SessionTimer
|
|
216
|
-
const sessionTimerContainer = this.container.querySelector('#session-timer-container');
|
|
217
|
-
if (sessionTimerContainer && window.SessionTimer) {
|
|
218
|
-
this.components.sessionTimer = new SessionTimer(
|
|
219
|
-
sessionTimerContainer,
|
|
220
|
-
this.dataService,
|
|
221
|
-
this.stateService
|
|
222
|
-
);
|
|
223
|
-
await this.components.sessionTimer.initialize();
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
// Initialize ConversationTable
|
|
227
|
-
const tableContainer = this.container.querySelector('#conversations-table');
|
|
228
|
-
this.components.conversationTable = new ConversationTable(
|
|
229
|
-
tableContainer,
|
|
230
|
-
this.dataService,
|
|
231
|
-
this.stateService
|
|
232
|
-
);
|
|
233
|
-
await this.components.conversationTable.initialize();
|
|
234
|
-
|
|
235
|
-
// Initialize Charts (if available)
|
|
236
|
-
if (this.chartService) {
|
|
237
|
-
await this.initializeCharts();
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
/**
|
|
242
|
-
* Initialize charts
|
|
243
|
-
*/
|
|
244
|
-
async initializeCharts() {
|
|
245
|
-
const chartCanvas = this.container.querySelector('#usage-chart');
|
|
246
|
-
if (chartCanvas) {
|
|
247
|
-
this.components.usageChart = new Chart(chartCanvas, {
|
|
248
|
-
type: 'line',
|
|
249
|
-
data: {
|
|
250
|
-
labels: [],
|
|
251
|
-
datasets: [{
|
|
252
|
-
label: 'Conversations',
|
|
253
|
-
data: [],
|
|
254
|
-
borderColor: '#3fb950',
|
|
255
|
-
backgroundColor: 'rgba(63, 185, 80, 0.1)',
|
|
256
|
-
tension: 0.4
|
|
257
|
-
}]
|
|
258
|
-
},
|
|
259
|
-
options: {
|
|
260
|
-
responsive: true,
|
|
261
|
-
maintainAspectRatio: false,
|
|
262
|
-
scales: {
|
|
263
|
-
y: {
|
|
264
|
-
beginAtZero: true,
|
|
265
|
-
grid: {
|
|
266
|
-
color: '#30363d'
|
|
267
|
-
},
|
|
268
|
-
ticks: {
|
|
269
|
-
color: '#c9d1d9'
|
|
270
|
-
}
|
|
271
|
-
},
|
|
272
|
-
x: {
|
|
273
|
-
grid: {
|
|
274
|
-
color: '#30363d'
|
|
275
|
-
},
|
|
276
|
-
ticks: {
|
|
277
|
-
color: '#c9d1d9'
|
|
278
|
-
}
|
|
279
|
-
}
|
|
280
|
-
},
|
|
281
|
-
plugins: {
|
|
282
|
-
legend: {
|
|
283
|
-
labels: {
|
|
284
|
-
color: '#c9d1d9'
|
|
285
|
-
}
|
|
286
|
-
}
|
|
287
|
-
}
|
|
288
|
-
}
|
|
289
|
-
});
|
|
290
|
-
}
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
/**
|
|
294
|
-
* Bind event listeners
|
|
295
|
-
*/
|
|
296
|
-
bindEvents() {
|
|
297
|
-
// Refresh button
|
|
298
|
-
const refreshBtn = this.container.querySelector('#refresh-btn');
|
|
299
|
-
refreshBtn.addEventListener('click', () => this.refreshData());
|
|
300
|
-
|
|
301
|
-
// Notifications button
|
|
302
|
-
const notificationsBtn = this.container.querySelector('#notifications-btn');
|
|
303
|
-
notificationsBtn.addEventListener('click', () => this.toggleNotifications());
|
|
304
|
-
|
|
305
|
-
// Export button
|
|
306
|
-
const exportBtn = this.container.querySelector('#export-btn');
|
|
307
|
-
exportBtn.addEventListener('click', () => this.exportData());
|
|
308
|
-
|
|
309
|
-
// Chart period selector
|
|
310
|
-
const chartPeriod = this.container.querySelector('#chart-period');
|
|
311
|
-
chartPeriod.addEventListener('change', (e) => this.updateChartPeriod(e.target.value));
|
|
312
|
-
|
|
313
|
-
// Error banner close
|
|
314
|
-
const errorClose = this.container.querySelector('#error-close');
|
|
315
|
-
errorClose.addEventListener('click', () => this.clearError());
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
/**
|
|
319
|
-
* Load initial data
|
|
320
|
-
*/
|
|
321
|
-
async loadInitialData() {
|
|
322
|
-
try {
|
|
323
|
-
const [conversationsData, statesData] = await Promise.all([
|
|
324
|
-
this.dataService.getConversations(),
|
|
325
|
-
this.dataService.getConversationStates()
|
|
326
|
-
]);
|
|
327
|
-
|
|
328
|
-
this.stateService.updateConversations(conversationsData.conversations);
|
|
329
|
-
this.stateService.updateSummary(conversationsData.summary);
|
|
330
|
-
this.stateService.updateConversationStates(statesData);
|
|
331
|
-
|
|
332
|
-
this.updateLastUpdateTime();
|
|
333
|
-
} catch (error) {
|
|
334
|
-
console.error('Error loading initial data:', error);
|
|
335
|
-
this.stateService.setError('Failed to load dashboard data');
|
|
336
|
-
}
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
/**
|
|
340
|
-
* Refresh all data
|
|
341
|
-
*/
|
|
342
|
-
async refreshData() {
|
|
343
|
-
const refreshBtn = this.container.querySelector('#refresh-btn');
|
|
344
|
-
refreshBtn.disabled = true;
|
|
345
|
-
refreshBtn.querySelector('.refresh-icon').style.animation = 'spin 1s linear infinite';
|
|
346
|
-
|
|
347
|
-
try {
|
|
348
|
-
this.dataService.clearCache();
|
|
349
|
-
await this.loadInitialData();
|
|
350
|
-
} catch (error) {
|
|
351
|
-
console.error('Error refreshing data:', error);
|
|
352
|
-
this.stateService.setError('Failed to refresh data');
|
|
353
|
-
} finally {
|
|
354
|
-
refreshBtn.disabled = false;
|
|
355
|
-
refreshBtn.querySelector('.refresh-icon').style.animation = '';
|
|
356
|
-
}
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
/**
|
|
360
|
-
* Update summary display
|
|
361
|
-
* @param {Object} summary - Summary data
|
|
362
|
-
*/
|
|
363
|
-
updateSummaryDisplay(summary) {
|
|
364
|
-
if (!summary) return;
|
|
365
|
-
|
|
366
|
-
const totalConvs = this.container.querySelector('#total-conversations');
|
|
367
|
-
const activeSessions = this.container.querySelector('#active-sessions');
|
|
368
|
-
const totalTokens = this.container.querySelector('#total-tokens');
|
|
369
|
-
const totalProjects = this.container.querySelector('#total-projects');
|
|
370
|
-
|
|
371
|
-
if (totalConvs) totalConvs.textContent = summary.totalConversations?.toLocaleString() || '0';
|
|
372
|
-
if (activeSessions) activeSessions.textContent = summary.activeConversations?.toLocaleString() || '0';
|
|
373
|
-
if (totalTokens) totalTokens.textContent = summary.totalTokens?.toLocaleString() || '0';
|
|
374
|
-
if (totalProjects) totalProjects.textContent = summary.totalProjects?.toLocaleString() || '0';
|
|
375
|
-
|
|
376
|
-
// Update change indicators
|
|
377
|
-
const conversationsChange = this.container.querySelector('#conversations-change');
|
|
378
|
-
const sessionsChange = this.container.querySelector('#sessions-change');
|
|
379
|
-
const tokensChange = this.container.querySelector('#tokens-change');
|
|
380
|
-
const projectsChange = this.container.querySelector('#projects-change');
|
|
381
|
-
|
|
382
|
-
if (conversationsChange) conversationsChange.textContent = `+${summary.totalConversations || 0} total`;
|
|
383
|
-
if (sessionsChange) sessionsChange.textContent = `${summary.activeConversations || 0} running`;
|
|
384
|
-
if (tokensChange) tokensChange.textContent = `${summary.avgTokensPerConversation || 0} avg/conv`;
|
|
385
|
-
if (projectsChange) projectsChange.textContent = `${summary.activeProjects || 0} active`;
|
|
386
|
-
}
|
|
387
|
-
|
|
388
|
-
/**
|
|
389
|
-
* Update conversation states
|
|
390
|
-
* @param {Object} states - Conversation states
|
|
391
|
-
*/
|
|
392
|
-
updateConversationStates(states) {
|
|
393
|
-
// Update status dot based on active conversations
|
|
394
|
-
const statusDot = this.container.querySelector('.status-dot');
|
|
395
|
-
const activeCount = Object.values(states).filter(state => state === 'active').length;
|
|
396
|
-
|
|
397
|
-
if (activeCount > 0) {
|
|
398
|
-
statusDot.style.background = '#3fb950';
|
|
399
|
-
statusDot.style.animation = 'pulse 2s infinite';
|
|
400
|
-
} else {
|
|
401
|
-
statusDot.style.background = '#f85149';
|
|
402
|
-
statusDot.style.animation = 'none';
|
|
403
|
-
}
|
|
404
|
-
}
|
|
405
|
-
|
|
406
|
-
/**
|
|
407
|
-
* Handle conversation state change
|
|
408
|
-
* @param {Object} state - New state
|
|
409
|
-
*/
|
|
410
|
-
handleConversationStateChange(state) {
|
|
411
|
-
// Show notification for state changes
|
|
412
|
-
this.showNotification(`Conversation state changed`, 'info');
|
|
413
|
-
this.updateLastUpdateTime();
|
|
414
|
-
}
|
|
415
|
-
|
|
416
|
-
/**
|
|
417
|
-
* Update loading state
|
|
418
|
-
* @param {boolean} isLoading - Loading state
|
|
419
|
-
*/
|
|
420
|
-
updateLoadingState(isLoading) {
|
|
421
|
-
const loadingOverlay = this.container.querySelector('#loading-overlay');
|
|
422
|
-
if (loadingOverlay) {
|
|
423
|
-
loadingOverlay.style.display = isLoading ? 'flex' : 'none';
|
|
424
|
-
} else {
|
|
425
|
-
// If the DOM isn't ready yet, show a temporary loading state
|
|
426
|
-
if (isLoading && this.container) {
|
|
427
|
-
this.container.style.opacity = '0.5';
|
|
428
|
-
this.container.style.pointerEvents = 'none';
|
|
429
|
-
} else if (this.container) {
|
|
430
|
-
this.container.style.opacity = '1';
|
|
431
|
-
this.container.style.pointerEvents = 'auto';
|
|
432
|
-
}
|
|
433
|
-
}
|
|
434
|
-
}
|
|
435
|
-
|
|
436
|
-
/**
|
|
437
|
-
* Update error state
|
|
438
|
-
* @param {Error|string} error - Error object or message
|
|
439
|
-
*/
|
|
440
|
-
updateErrorState(error) {
|
|
441
|
-
const errorBanner = this.container.querySelector('#error-banner');
|
|
442
|
-
const errorText = this.container.querySelector('.error-text');
|
|
443
|
-
|
|
444
|
-
if (errorBanner && errorText) {
|
|
445
|
-
if (error) {
|
|
446
|
-
errorText.textContent = error.message || error;
|
|
447
|
-
errorBanner.style.display = 'flex';
|
|
448
|
-
} else {
|
|
449
|
-
errorBanner.style.display = 'none';
|
|
450
|
-
}
|
|
451
|
-
} else if (error) {
|
|
452
|
-
// If DOM isn't ready, log error for now
|
|
453
|
-
console.error('Dashboard error (DOM not ready):', error.message || error);
|
|
454
|
-
}
|
|
455
|
-
}
|
|
456
|
-
|
|
457
|
-
/**
|
|
458
|
-
* Clear error state
|
|
459
|
-
*/
|
|
460
|
-
clearError() {
|
|
461
|
-
this.stateService.clearError();
|
|
462
|
-
}
|
|
463
|
-
|
|
464
|
-
/**
|
|
465
|
-
* Toggle notifications
|
|
466
|
-
*/
|
|
467
|
-
toggleNotifications() {
|
|
468
|
-
const notificationsBtn = this.container.querySelector('#notifications-btn');
|
|
469
|
-
const isEnabled = notificationsBtn.classList.toggle('active');
|
|
470
|
-
|
|
471
|
-
if (isEnabled) {
|
|
472
|
-
this.requestNotificationPermission();
|
|
473
|
-
}
|
|
474
|
-
}
|
|
475
|
-
|
|
476
|
-
/**
|
|
477
|
-
* Request notification permission
|
|
478
|
-
*/
|
|
479
|
-
async requestNotificationPermission() {
|
|
480
|
-
if ('Notification' in window) {
|
|
481
|
-
const permission = await Notification.requestPermission();
|
|
482
|
-
if (permission === 'granted') {
|
|
483
|
-
this.showNotification('Notifications enabled', 'success');
|
|
484
|
-
}
|
|
485
|
-
}
|
|
486
|
-
}
|
|
487
|
-
|
|
488
|
-
/**
|
|
489
|
-
* Show notification
|
|
490
|
-
* @param {string} message - Notification message
|
|
491
|
-
* @param {string} type - Notification type
|
|
492
|
-
*/
|
|
493
|
-
showNotification(message, type = 'info') {
|
|
494
|
-
if ('Notification' in window && Notification.permission === 'granted') {
|
|
495
|
-
new Notification(`Claude Code Analytics`, {
|
|
496
|
-
body: message,
|
|
497
|
-
icon: '/favicon.ico'
|
|
498
|
-
});
|
|
499
|
-
}
|
|
500
|
-
}
|
|
501
|
-
|
|
502
|
-
/**
|
|
503
|
-
* Export data
|
|
504
|
-
*/
|
|
505
|
-
exportData() {
|
|
506
|
-
const conversations = this.stateService.getStateProperty('conversations');
|
|
507
|
-
const dataStr = JSON.stringify(conversations, null, 2);
|
|
508
|
-
const dataBlob = new Blob([dataStr], { type: 'application/json' });
|
|
509
|
-
const url = URL.createObjectURL(dataBlob);
|
|
510
|
-
|
|
511
|
-
const link = document.createElement('a');
|
|
512
|
-
link.href = url;
|
|
513
|
-
link.download = `claude-analytics-${new Date().toISOString().split('T')[0]}.json`;
|
|
514
|
-
link.click();
|
|
515
|
-
|
|
516
|
-
URL.revokeObjectURL(url);
|
|
517
|
-
}
|
|
518
|
-
|
|
519
|
-
/**
|
|
520
|
-
* Update chart period
|
|
521
|
-
* @param {string} period - New period
|
|
522
|
-
*/
|
|
523
|
-
updateChartPeriod(period) {
|
|
524
|
-
// Update chart with new period data
|
|
525
|
-
console.log('Updating chart period to:', period);
|
|
526
|
-
// Implementation would update the chart with filtered data
|
|
527
|
-
}
|
|
528
|
-
|
|
529
|
-
/**
|
|
530
|
-
* Update last update time
|
|
531
|
-
*/
|
|
532
|
-
updateLastUpdateTime() {
|
|
533
|
-
const lastUpdateText = this.container.querySelector('#last-update-text');
|
|
534
|
-
if (lastUpdateText) {
|
|
535
|
-
lastUpdateText.textContent = new Date().toLocaleTimeString();
|
|
536
|
-
}
|
|
537
|
-
}
|
|
538
|
-
|
|
539
|
-
/**
|
|
540
|
-
* Start periodic refresh
|
|
541
|
-
*/
|
|
542
|
-
startPeriodicRefresh() {
|
|
543
|
-
this.refreshInterval = setInterval(async () => {
|
|
544
|
-
try {
|
|
545
|
-
const statesData = await this.dataService.getConversationStates();
|
|
546
|
-
this.stateService.updateConversationStates(statesData);
|
|
547
|
-
this.updateLastUpdateTime();
|
|
548
|
-
} catch (error) {
|
|
549
|
-
console.error('Error during periodic refresh:', error);
|
|
550
|
-
}
|
|
551
|
-
}, 30000); // Refresh every 30 seconds
|
|
552
|
-
}
|
|
553
|
-
|
|
554
|
-
/**
|
|
555
|
-
* Stop periodic refresh
|
|
556
|
-
*/
|
|
557
|
-
stopPeriodicRefresh() {
|
|
558
|
-
if (this.refreshInterval) {
|
|
559
|
-
clearInterval(this.refreshInterval);
|
|
560
|
-
this.refreshInterval = null;
|
|
561
|
-
}
|
|
562
|
-
}
|
|
563
|
-
|
|
564
|
-
/**
|
|
565
|
-
* Cleanup and destroy dashboard
|
|
566
|
-
*/
|
|
567
|
-
destroy() {
|
|
568
|
-
this.stopPeriodicRefresh();
|
|
569
|
-
|
|
570
|
-
// Cleanup components
|
|
571
|
-
Object.values(this.components).forEach(component => {
|
|
572
|
-
if (component.destroy) {
|
|
573
|
-
component.destroy();
|
|
574
|
-
}
|
|
575
|
-
});
|
|
576
|
-
|
|
577
|
-
// Unsubscribe from state changes
|
|
578
|
-
if (this.unsubscribe) {
|
|
579
|
-
this.unsubscribe();
|
|
580
|
-
}
|
|
581
|
-
|
|
582
|
-
this.isInitialized = false;
|
|
583
|
-
}
|
|
584
|
-
}
|
|
585
|
-
|
|
586
|
-
// Export for module use
|
|
587
|
-
if (typeof module !== 'undefined' && module.exports) {
|
|
588
|
-
module.exports = Dashboard;
|
|
589
|
-
}
|