@autocode-cli/autocode 0.1.9 → 0.1.11

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.
Files changed (108) hide show
  1. package/README.md +2 -1
  2. package/dist/cli/commands/init.js +1 -1
  3. package/dist/cli/commands/init.js.map +1 -1
  4. package/dist/cli/commands/new.d.ts.map +1 -1
  5. package/dist/cli/commands/new.js +20 -2
  6. package/dist/cli/commands/new.js.map +1 -1
  7. package/dist/cli/commands/serve.js +1 -1
  8. package/dist/cli/commands/serve.js.map +1 -1
  9. package/dist/cli/commands/stats.d.ts +9 -0
  10. package/dist/cli/commands/stats.d.ts.map +1 -0
  11. package/dist/cli/commands/stats.js +108 -0
  12. package/dist/cli/commands/stats.js.map +1 -0
  13. package/dist/cli/parser.d.ts.map +1 -1
  14. package/dist/cli/parser.js +2 -0
  15. package/dist/cli/parser.js.map +1 -1
  16. package/dist/server/api.d.ts.map +1 -1
  17. package/dist/server/api.js +25 -13
  18. package/dist/server/api.js.map +1 -1
  19. package/dist/server/dashboard/pages/index.d.ts +1 -0
  20. package/dist/server/dashboard/pages/index.d.ts.map +1 -1
  21. package/dist/server/dashboard/pages/index.js +1 -0
  22. package/dist/server/dashboard/pages/index.js.map +1 -1
  23. package/dist/server/dashboard/pages/stats-page.d.ts +8 -0
  24. package/dist/server/dashboard/pages/stats-page.d.ts.map +1 -0
  25. package/dist/server/dashboard/pages/stats-page.js +624 -0
  26. package/dist/server/dashboard/pages/stats-page.js.map +1 -0
  27. package/dist/server/dashboard/scripts/index.d.ts.map +1 -1
  28. package/dist/server/dashboard/scripts/index.js +30 -1
  29. package/dist/server/dashboard/scripts/index.js.map +1 -1
  30. package/dist/server/dashboard/styles/base.d.ts.map +1 -1
  31. package/dist/server/dashboard/styles/base.js +9 -0
  32. package/dist/server/dashboard/styles/base.js.map +1 -1
  33. package/dist/server/dashboard.d.ts +1 -1
  34. package/dist/server/dashboard.d.ts.map +1 -1
  35. package/dist/server/dashboard.js +1 -1
  36. package/dist/server/dashboard.js.map +1 -1
  37. package/dist/server/index.d.ts.map +1 -1
  38. package/dist/server/index.js +8 -1
  39. package/dist/server/index.js.map +1 -1
  40. package/dist/services/claude.d.ts +9 -0
  41. package/dist/services/claude.d.ts.map +1 -1
  42. package/dist/services/claude.js +105 -2
  43. package/dist/services/claude.js.map +1 -1
  44. package/dist/services/stats.d.ts +58 -0
  45. package/dist/services/stats.d.ts.map +1 -0
  46. package/dist/services/stats.js +196 -0
  47. package/dist/services/stats.js.map +1 -0
  48. package/dist/utils/fs.js +1 -1
  49. package/dist/utils/fs.js.map +1 -1
  50. package/package.json +1 -1
  51. package/templates/prompts/backlog.en.md +4 -1
  52. package/templates/prompts/backlog.fr.md +4 -1
  53. package/templates/prompts/changelog.en.md +6 -4
  54. package/templates/prompts/changelog.fr.md +6 -4
  55. package/templates/prompts/deploy-prod.en.md +4 -1
  56. package/templates/prompts/deploy-prod.fr.md +4 -1
  57. package/templates/prompts/deploy-staging.en.md +6 -3
  58. package/templates/prompts/deploy-staging.fr.md +6 -3
  59. package/templates/prompts/design.en.md +4 -1
  60. package/templates/prompts/design.fr.md +4 -1
  61. package/templates/prompts/dev.en.md +7 -5
  62. package/templates/prompts/dev.fr.md +7 -5
  63. package/templates/prompts/done.en.md +3 -1
  64. package/templates/prompts/done.fr.md +3 -1
  65. package/templates/prompts/git-commit.en.md +4 -1
  66. package/templates/prompts/git-commit.fr.md +4 -1
  67. package/templates/prompts/git-push.en.md +4 -1
  68. package/templates/prompts/git-push.fr.md +4 -1
  69. package/templates/prompts/git-tag.en.md +4 -1
  70. package/templates/prompts/git-tag.fr.md +4 -1
  71. package/templates/prompts/in-progress.en.md +6 -5
  72. package/templates/prompts/in-progress.fr.md +6 -5
  73. package/templates/prompts/qualification.en.md +4 -1
  74. package/templates/prompts/qualification.fr.md +4 -1
  75. package/templates/prompts/ready.en.md +6 -5
  76. package/templates/prompts/ready.fr.md +6 -5
  77. package/templates/prompts/retest-cypress.en.md +6 -4
  78. package/templates/prompts/retest-cypress.fr.md +6 -4
  79. package/templates/prompts/retest-playwright.en.md +4 -1
  80. package/templates/prompts/retest-playwright.fr.md +4 -1
  81. package/templates/prompts/retest.en.md +4 -1
  82. package/templates/prompts/retest.fr.md +4 -1
  83. package/templates/prompts/review-best-practices.en.md +6 -4
  84. package/templates/prompts/review-best-practices.fr.md +6 -4
  85. package/templates/prompts/review-code.en.md +4 -1
  86. package/templates/prompts/review-code.fr.md +4 -1
  87. package/templates/prompts/review-consistency.en.md +6 -4
  88. package/templates/prompts/review-consistency.fr.md +6 -4
  89. package/templates/prompts/review-no-duplication.en.md +6 -4
  90. package/templates/prompts/review-no-duplication.fr.md +6 -4
  91. package/templates/prompts/review-security.en.md +6 -4
  92. package/templates/prompts/review-security.fr.md +6 -4
  93. package/templates/prompts/specification.en.md +4 -1
  94. package/templates/prompts/specification.fr.md +4 -1
  95. package/templates/prompts/splitter.en.md +9 -1
  96. package/templates/prompts/splitter.fr.md +9 -2
  97. package/templates/prompts/testing-cypress.en.md +6 -4
  98. package/templates/prompts/testing-cypress.fr.md +6 -4
  99. package/templates/prompts/testing-integration.en.md +6 -4
  100. package/templates/prompts/testing-integration.fr.md +6 -4
  101. package/templates/prompts/testing-playwright.en.md +4 -1
  102. package/templates/prompts/testing-playwright.fr.md +4 -1
  103. package/templates/prompts/testing-unit.en.md +6 -4
  104. package/templates/prompts/testing-unit.fr.md +6 -4
  105. package/templates/prompts/update-docs.en.md +6 -4
  106. package/templates/prompts/update-docs.fr.md +6 -4
  107. package/templates/prompts/validate-staging.en.md +11 -9
  108. package/templates/prompts/validate-staging.fr.md +11 -9
@@ -0,0 +1,624 @@
1
+ /**
2
+ * Stats page - Claude usage statistics dashboard
3
+ */
4
+ import { getStyles } from '../styles/index.js';
5
+ /**
6
+ * Generate the stats page HTML
7
+ */
8
+ export function generateStatsPage() {
9
+ return `<!DOCTYPE html>
10
+ <html lang="fr">
11
+ <head>
12
+ <meta charset="UTF-8">
13
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
14
+ <title>Stats - AutoCode</title>
15
+ <style>
16
+ ${getStyles()}
17
+
18
+ .stats-page {
19
+ max-width: 1200px;
20
+ margin: 0 auto;
21
+ padding: 20px;
22
+ }
23
+
24
+ .stats-header {
25
+ display: flex;
26
+ justify-content: space-between;
27
+ align-items: center;
28
+ margin-bottom: 30px;
29
+ }
30
+
31
+ .stats-header h1 {
32
+ margin: 0;
33
+ font-size: 24px;
34
+ display: flex;
35
+ align-items: center;
36
+ gap: 10px;
37
+ }
38
+
39
+ .header-actions {
40
+ display: flex;
41
+ align-items: center;
42
+ gap: 12px;
43
+ }
44
+
45
+ .lang-switcher {
46
+ display: flex;
47
+ gap: 4px;
48
+ background: #252525;
49
+ padding: 4px;
50
+ border-radius: 6px;
51
+ }
52
+
53
+ .lang-btn {
54
+ padding: 4px 10px;
55
+ border: none;
56
+ background: transparent;
57
+ color: #888;
58
+ cursor: pointer;
59
+ border-radius: 4px;
60
+ font-size: 12px;
61
+ font-weight: 500;
62
+ transition: all 0.2s;
63
+ }
64
+
65
+ .lang-btn:hover {
66
+ color: #fff;
67
+ }
68
+
69
+ .lang-btn.active {
70
+ background: #a855f7;
71
+ color: #fff;
72
+ }
73
+
74
+ .refresh-indicator {
75
+ width: 8px;
76
+ height: 8px;
77
+ border-radius: 50%;
78
+ background: #333;
79
+ transition: all 0.3s ease;
80
+ }
81
+
82
+ .refresh-indicator.loading {
83
+ background: #a855f7;
84
+ animation: pulse-dot 0.5s ease;
85
+ }
86
+
87
+ @keyframes pulse-dot {
88
+ 0% { transform: scale(1); opacity: 1; }
89
+ 50% { transform: scale(1.5); opacity: 0.7; }
90
+ 100% { transform: scale(1); opacity: 1; }
91
+ }
92
+
93
+ .stats-cards {
94
+ display: grid;
95
+ grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
96
+ gap: 20px;
97
+ margin-bottom: 30px;
98
+ }
99
+
100
+ .stats-card {
101
+ background: #1e1e1e;
102
+ border: 1px solid #333;
103
+ border-radius: 8px;
104
+ padding: 20px;
105
+ }
106
+
107
+ .stats-card h3 {
108
+ margin: 0 0 15px 0;
109
+ font-size: 14px;
110
+ color: #888;
111
+ text-transform: uppercase;
112
+ letter-spacing: 1px;
113
+ }
114
+
115
+ .stats-card .value {
116
+ font-size: 32px;
117
+ font-weight: bold;
118
+ color: #fff;
119
+ }
120
+
121
+ .stats-card .value.small {
122
+ font-size: 18px;
123
+ }
124
+
125
+ .stats-card .label {
126
+ font-size: 12px;
127
+ color: #666;
128
+ margin-top: 5px;
129
+ }
130
+
131
+ .stats-grid {
132
+ display: grid;
133
+ grid-template-columns: 1fr 1fr;
134
+ gap: 15px;
135
+ }
136
+
137
+ .stats-item {
138
+ display: flex;
139
+ flex-direction: column;
140
+ }
141
+
142
+ .stats-item .value {
143
+ font-size: 24px;
144
+ font-weight: bold;
145
+ }
146
+
147
+ .stats-item .label {
148
+ font-size: 12px;
149
+ color: #888;
150
+ }
151
+
152
+ .profile-badge {
153
+ display: inline-flex;
154
+ align-items: center;
155
+ gap: 8px;
156
+ padding: 8px 12px;
157
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
158
+ border-radius: 20px;
159
+ font-weight: 500;
160
+ }
161
+
162
+ .model-badge {
163
+ display: inline-block;
164
+ padding: 4px 8px;
165
+ background: #333;
166
+ border-radius: 4px;
167
+ font-family: monospace;
168
+ font-size: 14px;
169
+ }
170
+
171
+ .history-table {
172
+ width: 100%;
173
+ border-collapse: collapse;
174
+ margin-top: 10px;
175
+ }
176
+
177
+ .history-table th,
178
+ .history-table td {
179
+ padding: 12px;
180
+ text-align: left;
181
+ border-bottom: 1px solid #333;
182
+ }
183
+
184
+ .history-table th {
185
+ color: #888;
186
+ font-weight: 500;
187
+ font-size: 12px;
188
+ text-transform: uppercase;
189
+ }
190
+
191
+ .history-table tr:hover {
192
+ background: #252525;
193
+ }
194
+
195
+ .history-table .ticket-link {
196
+ color: #a855f7;
197
+ text-decoration: none;
198
+ }
199
+
200
+ .history-table .ticket-link:hover {
201
+ text-decoration: underline;
202
+ }
203
+
204
+ .tokens-badge {
205
+ display: inline-flex;
206
+ gap: 8px;
207
+ font-size: 13px;
208
+ }
209
+
210
+ .tokens-badge .in {
211
+ color: #22c55e;
212
+ }
213
+
214
+ .tokens-badge .out {
215
+ color: #3b82f6;
216
+ }
217
+
218
+ .empty-state {
219
+ text-align: center;
220
+ padding: 40px;
221
+ color: #666;
222
+ }
223
+
224
+ .info-box {
225
+ display: flex;
226
+ gap: 12px;
227
+ margin-top: 20px;
228
+ padding: 16px;
229
+ background: rgba(59, 130, 246, 0.1);
230
+ border: 1px solid rgba(59, 130, 246, 0.3);
231
+ border-radius: 8px;
232
+ font-size: 13px;
233
+ line-height: 1.5;
234
+ color: #94a3b8;
235
+ }
236
+
237
+ .info-box .icon {
238
+ flex-shrink: 0;
239
+ width: 20px;
240
+ height: 20px;
241
+ display: flex;
242
+ align-items: center;
243
+ justify-content: center;
244
+ background: #3b82f6;
245
+ color: #fff;
246
+ border-radius: 50%;
247
+ font-size: 12px;
248
+ font-weight: bold;
249
+ }
250
+
251
+ .info-box strong {
252
+ color: #e2e8f0;
253
+ }
254
+
255
+ .info-box code {
256
+ background: rgba(0,0,0,0.3);
257
+ padding: 2px 6px;
258
+ border-radius: 4px;
259
+ font-family: monospace;
260
+ color: #22c55e;
261
+ }
262
+
263
+ .btn-back {
264
+ display: inline-flex;
265
+ align-items: center;
266
+ gap: 8px;
267
+ padding: 8px 16px;
268
+ background: #333;
269
+ color: #fff;
270
+ border: none;
271
+ border-radius: 6px;
272
+ text-decoration: none;
273
+ font-size: 14px;
274
+ }
275
+
276
+ .btn-back:hover {
277
+ background: #444;
278
+ }
279
+
280
+ .processing-indicator {
281
+ display: inline-flex;
282
+ align-items: center;
283
+ gap: 8px;
284
+ padding: 4px 8px;
285
+ background: rgba(168, 85, 247, 0.2);
286
+ border-radius: 4px;
287
+ font-size: 12px;
288
+ color: #a855f7;
289
+ }
290
+
291
+ .processing-indicator::before {
292
+ content: '';
293
+ width: 8px;
294
+ height: 8px;
295
+ border-radius: 50%;
296
+ background: #a855f7;
297
+ animation: pulse 1.5s infinite;
298
+ }
299
+
300
+ @keyframes pulse {
301
+ 0%, 100% { opacity: 1; }
302
+ 50% { opacity: 0.5; }
303
+ }
304
+ </style>
305
+ </head>
306
+ <body>
307
+ <div class="stats-page">
308
+ <div class="stats-header">
309
+ <h1><span data-i18n="title"></span> <span id="refresh-indicator" class="refresh-indicator"></span></h1>
310
+ <div class="header-actions">
311
+ <div class="lang-switcher">
312
+ <button class="lang-btn" data-lang="fr">FR</button>
313
+ <button class="lang-btn" data-lang="en">EN</button>
314
+ </div>
315
+ <a href="/" class="btn-back" data-i18n="back"></a>
316
+ </div>
317
+ </div>
318
+
319
+ <div class="stats-cards">
320
+ <div class="stats-card">
321
+ <h3 data-i18n="profile"></h3>
322
+ <div class="profile-badge" id="profile">-</div>
323
+ <div style="margin-top: 15px">
324
+ <span class="model-badge" id="model">-</span>
325
+ </div>
326
+ </div>
327
+
328
+ <div class="stats-card">
329
+ <h3 data-i18n="session"></h3>
330
+ <div class="stats-grid">
331
+ <div class="stats-item">
332
+ <span class="value" id="session-tokens">-</span>
333
+ <span class="label" data-i18n="totalTokens"></span>
334
+ </div>
335
+ <div class="stats-item">
336
+ <span class="value" id="session-calls">-</span>
337
+ <span class="label" data-i18n="calls"></span>
338
+ </div>
339
+ <div class="stats-item">
340
+ <span class="value" id="session-cost" style="color: #22c55e;">-</span>
341
+ <span class="label" data-i18n="cost"></span>
342
+ </div>
343
+ </div>
344
+ <div class="label" id="session-started" style="margin-top: 10px;"></div>
345
+ </div>
346
+
347
+ <div class="stats-card">
348
+ <h3 data-i18n="totals"></h3>
349
+ <div class="stats-grid">
350
+ <div class="stats-item">
351
+ <span class="value" id="total-tokens">-</span>
352
+ <span class="label" data-i18n="totalTokens"></span>
353
+ </div>
354
+ <div class="stats-item">
355
+ <span class="value" id="total-calls">-</span>
356
+ <span class="label" data-i18n="calls"></span>
357
+ </div>
358
+ <div class="stats-item">
359
+ <span class="value" id="total-cost" style="color: #22c55e;">-</span>
360
+ <span class="label" data-i18n="cost"></span>
361
+ </div>
362
+ </div>
363
+ </div>
364
+
365
+ <div class="stats-card" id="processing-card" style="display: none;">
366
+ <h3 data-i18n="processing"></h3>
367
+ <div id="processing-tickets"></div>
368
+ </div>
369
+ </div>
370
+
371
+ <div class="stats-card">
372
+ <h3 data-i18n="history"></h3>
373
+ <div id="history-container">
374
+ <div class="empty-state" data-i18n="loading"></div>
375
+ </div>
376
+ <div class="info-box">
377
+ <span class="icon">i</span>
378
+ <div id="cost-explanation"></div>
379
+ </div>
380
+ </div>
381
+ </div>
382
+
383
+ <script>
384
+ // ========================================
385
+ // TRANSLATIONS
386
+ // ========================================
387
+ const translations = {
388
+ fr: {
389
+ title: 'Statistiques Claude',
390
+ profile: 'Profil',
391
+ model: 'Modèle',
392
+ session: 'Session actuelle',
393
+ totals: 'Totaux',
394
+ tokensInput: 'Tokens entrée',
395
+ tokensOutput: 'Tokens sortie',
396
+ totalTokens: 'Total tokens',
397
+ calls: 'Appels',
398
+ cost: 'Coût',
399
+ history: 'Historique des appels',
400
+ noHistory: 'Aucun appel enregistré',
401
+ back: 'Retour au dashboard',
402
+ startedAt: 'Démarré le',
403
+ ticket: 'Ticket',
404
+ column: 'Colonne',
405
+ duration: 'Durée',
406
+ date: 'Date',
407
+ loading: 'Chargement...',
408
+ processing: 'En cours',
409
+ costExplanation: '<strong>Pourquoi le coût semble bas ?</strong> Claude utilise un <strong>cache de prompts</strong> qui réduit significativement les coûts. Quand une partie du contexte a déjà été envoyée dans une conversation précédente, Claude ne la refacture pas au prix plein. Le coût affiché est le <strong>coût réel facturé</strong> par l\\'API Claude, qui inclut ces réductions de cache. C\\'est pour cela que <code>tokens × prix</code> ne correspond pas toujours au coût affiché - et c\\'est une bonne nouvelle pour votre portefeuille !',
410
+ },
411
+ en: {
412
+ title: 'Claude Statistics',
413
+ profile: 'Profile',
414
+ model: 'Model',
415
+ session: 'Current session',
416
+ totals: 'Totals',
417
+ tokensInput: 'Input tokens',
418
+ tokensOutput: 'Output tokens',
419
+ totalTokens: 'Total tokens',
420
+ calls: 'Calls',
421
+ cost: 'Cost',
422
+ history: 'Call history',
423
+ noHistory: 'No calls recorded',
424
+ back: 'Back to dashboard',
425
+ startedAt: 'Started at',
426
+ ticket: 'Ticket',
427
+ column: 'Column',
428
+ duration: 'Duration',
429
+ date: 'Date',
430
+ loading: 'Loading...',
431
+ processing: 'Processing',
432
+ costExplanation: '<strong>Why does the cost seem low?</strong> Claude uses <strong>prompt caching</strong> which significantly reduces costs. When part of the context was already sent in a previous conversation, Claude doesn\\'t charge full price again. The cost shown is the <strong>actual cost billed</strong> by the Claude API, including these cache discounts. That\\'s why <code>tokens × price</code> doesn\\'t always match the displayed cost - and that\\'s good news for your wallet!',
433
+ }
434
+ };
435
+
436
+ let currentLang = localStorage.getItem('autocode-lang') || 'fr';
437
+ let statsData = null;
438
+
439
+ function t(key) {
440
+ return translations[currentLang][key] || translations['en'][key] || key;
441
+ }
442
+
443
+ function switchLanguage(lang) {
444
+ currentLang = lang;
445
+ localStorage.setItem('autocode-lang', lang);
446
+ document.documentElement.lang = lang;
447
+
448
+ // Update lang switcher buttons
449
+ document.querySelectorAll('.lang-switcher .lang-btn').forEach(btn => {
450
+ btn.classList.toggle('active', btn.dataset.lang === lang);
451
+ });
452
+
453
+ // Update all elements with data-i18n
454
+ document.querySelectorAll('[data-i18n]').forEach(el => {
455
+ const key = el.getAttribute('data-i18n');
456
+ if (translations[lang] && translations[lang][key]) {
457
+ el.textContent = translations[lang][key];
458
+ }
459
+ });
460
+
461
+ // Update cost explanation (HTML content)
462
+ document.getElementById('cost-explanation').innerHTML = t('costExplanation');
463
+
464
+ // Update page title
465
+ document.title = t('title') + ' - AutoCode';
466
+
467
+ // Re-render history table if data exists
468
+ if (statsData) {
469
+ renderHistory(statsData);
470
+ renderSessionStarted(statsData);
471
+ }
472
+ }
473
+
474
+ // ========================================
475
+ // FORMATTING
476
+ // ========================================
477
+ function formatNumber(n) {
478
+ if (n >= 1000000) return (n / 1000000).toFixed(1) + 'M';
479
+ if (n >= 1000) return (n / 1000).toFixed(1) + 'K';
480
+ return n.toString();
481
+ }
482
+
483
+ function formatDate(iso) {
484
+ const d = new Date(iso);
485
+ return d.toLocaleString(currentLang === 'fr' ? 'fr-FR' : 'en-US');
486
+ }
487
+
488
+ function formatDuration(ms) {
489
+ if (ms < 1000) return ms + 'ms';
490
+ return (ms / 1000).toFixed(1) + 's';
491
+ }
492
+
493
+ // ========================================
494
+ // RENDERING
495
+ // ========================================
496
+ function renderSessionStarted(data) {
497
+ if (data.session.startedAt) {
498
+ document.getElementById('session-started').textContent = t('startedAt') + ': ' + formatDate(data.session.startedAt);
499
+ }
500
+ }
501
+
502
+ function renderHistory(data) {
503
+ if (data.history && data.history.length > 0) {
504
+ document.getElementById('history-container').innerHTML = \`
505
+ <table class="history-table">
506
+ <colgroup>
507
+ <col style="width: 100px;">
508
+ <col style="width: 200px;">
509
+ <col style="width: 100px;">
510
+ <col style="width: 100px;">
511
+ <col style="width: 80px;">
512
+ <col style="width: 70px;">
513
+ <col style="width: 160px;">
514
+ </colgroup>
515
+ <thead>
516
+ <tr>
517
+ <th>\${t('ticket')}</th>
518
+ <th>\${t('column')}</th>
519
+ <th>\${t('tokensInput')}</th>
520
+ <th>\${t('tokensOutput')}</th>
521
+ <th>\${t('cost')}</th>
522
+ <th>\${t('duration')}</th>
523
+ <th>\${t('date')}</th>
524
+ </tr>
525
+ </thead>
526
+ <tbody>
527
+ \${data.history.map(h => \`
528
+ <tr>
529
+ <td>\${h.ticket ? '<a href="/ticket/' + h.ticket + '" class="ticket-link">' + h.ticket + '</a>' : '-'}</td>
530
+ <td>\${h.column || '-'}</td>
531
+ <td>\${formatNumber(h.tokensInput || 0)}</td>
532
+ <td>\${formatNumber(h.tokensOutput || 0)}</td>
533
+ <td style="color: #22c55e;">\${h.costUsd ? '$' + h.costUsd.toFixed(4) : '-'}</td>
534
+ <td>\${formatDuration(h.duration)}</td>
535
+ <td>\${formatDate(h.timestamp)}</td>
536
+ </tr>
537
+ \`).join('')}
538
+ </tbody>
539
+ </table>
540
+ \`;
541
+ } else {
542
+ document.getElementById('history-container').innerHTML = '<div class="empty-state">' + t('noHistory') + '</div>';
543
+ }
544
+ }
545
+
546
+ // ========================================
547
+ // DATA LOADING
548
+ // ========================================
549
+ async function loadStats() {
550
+ const indicator = document.getElementById('refresh-indicator');
551
+ indicator.classList.add('loading');
552
+
553
+ try {
554
+ const res = await fetch('/api/stats');
555
+ const json = await res.json();
556
+
557
+ if (!json.success) throw new Error(json.error);
558
+
559
+ statsData = json.data;
560
+
561
+ // Profile & Model
562
+ document.getElementById('profile').textContent = statsData.profile || 'default';
563
+ document.getElementById('model').textContent = statsData.model || 'N/A';
564
+
565
+ // Session
566
+ const sessionTotal = (statsData.session.tokensInput || 0) + (statsData.session.tokensOutput || 0);
567
+ document.getElementById('session-tokens').textContent = formatNumber(sessionTotal);
568
+ document.getElementById('session-calls').textContent = statsData.session.callCount || 0;
569
+ document.getElementById('session-cost').textContent = '$' + (statsData.session.costUsd || 0).toFixed(2);
570
+ renderSessionStarted(statsData);
571
+
572
+ // Totals
573
+ const totalTokens = (statsData.totals.tokensInput || 0) + (statsData.totals.tokensOutput || 0);
574
+ document.getElementById('total-tokens').textContent = formatNumber(totalTokens);
575
+ document.getElementById('total-calls').textContent = statsData.totals.callCount || 0;
576
+ document.getElementById('total-cost').textContent = '$' + (statsData.totals.costUsd || 0).toFixed(2);
577
+
578
+ // Processing tickets
579
+ if (statsData.processingTickets && statsData.processingTickets.length > 0) {
580
+ document.getElementById('processing-card').style.display = 'block';
581
+ document.getElementById('processing-tickets').innerHTML = statsData.processingTickets
582
+ .map(ticket => '<div class="processing-indicator">' + ticket + '</div>')
583
+ .join('');
584
+ } else {
585
+ document.getElementById('processing-card').style.display = 'none';
586
+ }
587
+
588
+ // History
589
+ renderHistory(statsData);
590
+ } catch (e) {
591
+ console.error('Failed to load stats:', e);
592
+ } finally {
593
+ setTimeout(() => indicator.classList.remove('loading'), 500);
594
+ }
595
+ }
596
+
597
+ // ========================================
598
+ // INIT
599
+ // ========================================
600
+ // Initialize language switcher
601
+ document.querySelectorAll('.lang-switcher .lang-btn').forEach(btn => {
602
+ btn.addEventListener('click', () => switchLanguage(btn.dataset.lang));
603
+ });
604
+ switchLanguage(currentLang);
605
+
606
+ // Initial load
607
+ loadStats();
608
+
609
+ // Auto-refresh every 5 seconds
610
+ setInterval(loadStats, 5000);
611
+
612
+ // WebSocket for real-time updates
613
+ const ws = new WebSocket('ws://' + location.host + '/ws');
614
+ ws.onmessage = function(event) {
615
+ const data = JSON.parse(event.data);
616
+ if (data.type === 'claude_end') {
617
+ loadStats();
618
+ }
619
+ };
620
+ </script>
621
+ </body>
622
+ </html>`;
623
+ }
624
+ //# sourceMappingURL=stats-page.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stats-page.js","sourceRoot":"","sources":["../../../../src/server/dashboard/pages/stats-page.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAE/C;;GAEG;AACH,MAAM,UAAU,iBAAiB;IAC/B,OAAO;;;;;;;MAOH,SAAS,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;QA8lBT,CAAC;AACT,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/server/dashboard/scripts/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH;;GAEG;AACH,wBAAgB,SAAS,IAAI,MAAM,CAkyClC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/server/dashboard/scripts/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH;;GAEG;AACH,wBAAgB,SAAS,IAAI,MAAM,CA+zClC"}