@gajae-code/stats 0.1.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.
Files changed (67) hide show
  1. package/README.md +82 -0
  2. package/build.ts +84 -0
  3. package/dist/client/index.css +1 -0
  4. package/dist/client/index.html +13 -0
  5. package/dist/client/index.js +257 -0
  6. package/dist/client/styles.css +1159 -0
  7. package/dist/types/aggregator.d.ts +65 -0
  8. package/dist/types/client/App.d.ts +1 -0
  9. package/dist/types/client/api.d.ts +10 -0
  10. package/dist/types/client/components/BehaviorChart.d.ts +6 -0
  11. package/dist/types/client/components/BehaviorModelsTable.d.ts +7 -0
  12. package/dist/types/client/components/BehaviorSummary.d.ts +7 -0
  13. package/dist/types/client/components/ChartsContainer.d.ts +7 -0
  14. package/dist/types/client/components/CostChart.d.ts +6 -0
  15. package/dist/types/client/components/CostSummary.d.ts +6 -0
  16. package/dist/types/client/components/Header.d.ts +12 -0
  17. package/dist/types/client/components/ModelsTable.d.ts +8 -0
  18. package/dist/types/client/components/RequestDetail.d.ts +6 -0
  19. package/dist/types/client/components/RequestList.d.ts +8 -0
  20. package/dist/types/client/components/StatsGrid.d.ts +6 -0
  21. package/dist/types/client/components/chart-shared.d.ts +187 -0
  22. package/dist/types/client/components/models-table-shared.d.ts +195 -0
  23. package/dist/types/client/components/range-meta.d.ts +21 -0
  24. package/dist/types/client/index.d.ts +1 -0
  25. package/dist/types/client/types.d.ts +62 -0
  26. package/dist/types/client/useSystemTheme.d.ts +2 -0
  27. package/dist/types/db.d.ts +93 -0
  28. package/dist/types/index.d.ts +5 -0
  29. package/dist/types/parser.d.ts +40 -0
  30. package/dist/types/server.d.ts +7 -0
  31. package/dist/types/shared-types.d.ts +192 -0
  32. package/dist/types/sync-worker.d.ts +31 -0
  33. package/dist/types/types.d.ts +120 -0
  34. package/dist/types/user-metrics.d.ts +72 -0
  35. package/package.json +91 -0
  36. package/src/aggregator.ts +454 -0
  37. package/src/client/App.tsx +221 -0
  38. package/src/client/api.ts +65 -0
  39. package/src/client/components/BehaviorChart.tsx +189 -0
  40. package/src/client/components/BehaviorModelsTable.tsx +342 -0
  41. package/src/client/components/BehaviorSummary.tsx +95 -0
  42. package/src/client/components/ChartsContainer.tsx +221 -0
  43. package/src/client/components/CostChart.tsx +171 -0
  44. package/src/client/components/CostSummary.tsx +53 -0
  45. package/src/client/components/Header.tsx +72 -0
  46. package/src/client/components/ModelsTable.tsx +265 -0
  47. package/src/client/components/RequestDetail.tsx +172 -0
  48. package/src/client/components/RequestList.tsx +73 -0
  49. package/src/client/components/StatsGrid.tsx +135 -0
  50. package/src/client/components/chart-shared.tsx +320 -0
  51. package/src/client/components/models-table-shared.tsx +275 -0
  52. package/src/client/components/range-meta.ts +72 -0
  53. package/src/client/css.d.ts +1 -0
  54. package/src/client/index.tsx +6 -0
  55. package/src/client/styles.css +306 -0
  56. package/src/client/types.ts +78 -0
  57. package/src/client/useSystemTheme.ts +31 -0
  58. package/src/db.ts +1100 -0
  59. package/src/embedded-client.generated.txt +7 -0
  60. package/src/index.ts +182 -0
  61. package/src/parser.ts +334 -0
  62. package/src/server.ts +325 -0
  63. package/src/shared-types.ts +204 -0
  64. package/src/sync-worker.ts +40 -0
  65. package/src/types.ts +125 -0
  66. package/src/user-metrics.ts +686 -0
  67. package/tailwind.config.js +40 -0
@@ -0,0 +1,306 @@
1
+ @import "tailwindcss/index.css";
2
+ :root {
3
+ color-scheme: light;
4
+ --bg-page: #f8fafc;
5
+ --bg-surface: #ffffff;
6
+ --bg-elevated: #f1f5f9;
7
+ --bg-hover: rgba(15, 23, 42, 0.04);
8
+ --bg-active: rgba(15, 23, 42, 0.08);
9
+
10
+ --border-subtle: rgba(15, 23, 42, 0.08);
11
+ --border-default: rgba(15, 23, 42, 0.14);
12
+
13
+ --text-primary: #0f172a;
14
+ --text-secondary: #334155;
15
+ --text-muted: #64748b;
16
+
17
+ --accent-pink: #ec4899;
18
+ --accent-pink-glow: rgba(236, 72, 153, 0.26);
19
+ --accent-cyan: #0891b2;
20
+ --accent-cyan-glow: rgba(8, 145, 178, 0.24);
21
+ --accent-violet: #8b5cf6;
22
+ --accent-green: #16a34a;
23
+ --accent-amber: #d97706;
24
+ --accent-red: #dc2626;
25
+
26
+ --tab-active-highlight: inset 0 1px 0 rgba(15, 23, 42, 0.08);
27
+
28
+ --radius-sm: 6px;
29
+ --radius-md: 10px;
30
+ --radius-lg: 14px;
31
+ }
32
+
33
+ @media (prefers-color-scheme: dark) {
34
+ :root {
35
+ color-scheme: dark;
36
+ --bg-page: #0a0a0f;
37
+ --bg-surface: #111118;
38
+ --bg-elevated: #16161e;
39
+ --bg-hover: rgba(255, 255, 255, 0.03);
40
+ --bg-active: rgba(255, 255, 255, 0.06);
41
+
42
+ --border-subtle: rgba(255, 255, 255, 0.06);
43
+ --border-default: rgba(255, 255, 255, 0.1);
44
+
45
+ --text-primary: #f8fafc;
46
+ --text-secondary: #94a3b8;
47
+ --text-muted: #64748b;
48
+
49
+ --accent-pink: #ec4899;
50
+ --accent-pink-glow: rgba(236, 72, 153, 0.3);
51
+ --accent-cyan: #22d3ee;
52
+ --accent-cyan-glow: rgba(34, 211, 238, 0.3);
53
+ --accent-violet: #a78bfa;
54
+ --accent-green: #4ade80;
55
+ --accent-amber: #fbbf24;
56
+ --accent-red: #f87171;
57
+
58
+ --tab-active-highlight: inset 0 1px 0 rgba(255, 255, 255, 0.05);
59
+ }
60
+ }
61
+
62
+ @layer base {
63
+ * {
64
+ box-sizing: border-box;
65
+ }
66
+
67
+ html {
68
+ -webkit-font-smoothing: antialiased;
69
+ -moz-osx-font-smoothing: grayscale;
70
+ }
71
+
72
+ body {
73
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
74
+ background: var(--bg-page);
75
+ color: var(--text-primary);
76
+ margin: 0;
77
+ min-height: 100vh;
78
+ }
79
+
80
+ /* Custom scrollbar */
81
+ ::-webkit-scrollbar {
82
+ width: 8px;
83
+ height: 8px;
84
+ }
85
+
86
+ ::-webkit-scrollbar-track {
87
+ background: transparent;
88
+ }
89
+
90
+ ::-webkit-scrollbar-thumb {
91
+ background: var(--border-default);
92
+ border-radius: 4px;
93
+ }
94
+
95
+ ::-webkit-scrollbar-thumb:hover {
96
+ background: var(--text-muted);
97
+ }
98
+ }
99
+
100
+ @layer components {
101
+ .surface {
102
+ background: var(--bg-surface);
103
+ border: 1px solid var(--border-subtle);
104
+ border-radius: var(--radius-lg);
105
+ }
106
+
107
+ .surface-elevated {
108
+ background: var(--bg-elevated);
109
+ border: 1px solid var(--border-default);
110
+ border-radius: var(--radius-lg);
111
+ }
112
+
113
+ .btn {
114
+ display: inline-flex;
115
+ align-items: center;
116
+ justify-content: center;
117
+ gap: 8px;
118
+ padding: 8px 16px;
119
+ font-size: 14px;
120
+ font-weight: 500;
121
+ border-radius: var(--radius-md);
122
+ border: none;
123
+ cursor: pointer;
124
+ transition: all 0.15s ease;
125
+ }
126
+
127
+ .btn-primary {
128
+ background: linear-gradient(135deg, var(--accent-pink) 0%, #db2777 100%);
129
+ color: white;
130
+ box-shadow: 0 0 20px var(--accent-pink-glow);
131
+ }
132
+
133
+ .btn-primary:hover {
134
+ transform: translateY(-1px);
135
+ box-shadow: 0 4px 24px var(--accent-pink-glow);
136
+ }
137
+
138
+ .btn-primary:disabled {
139
+ opacity: 0.6;
140
+ cursor: not-allowed;
141
+ transform: none;
142
+ }
143
+
144
+ .btn-secondary {
145
+ background: var(--bg-elevated);
146
+ color: var(--text-primary);
147
+ border: 1px solid var(--border-default);
148
+ }
149
+
150
+ .btn-secondary:hover {
151
+ background: var(--bg-hover);
152
+ border-color: var(--border-default);
153
+ }
154
+
155
+ .tab-btn {
156
+ padding: 6px 16px;
157
+ font-size: 14px;
158
+ font-weight: 500;
159
+ color: var(--text-muted);
160
+ background: transparent;
161
+ border: none;
162
+ border-radius: var(--radius-md);
163
+ cursor: pointer;
164
+ transition: all 0.15s ease;
165
+ }
166
+
167
+ .tab-btn:hover {
168
+ color: var(--text-primary);
169
+ background: var(--bg-hover);
170
+ }
171
+
172
+ .tab-btn.active {
173
+ color: var(--text-primary);
174
+ background: var(--bg-elevated);
175
+ box-shadow: var(--tab-active-highlight);
176
+ }
177
+
178
+ .stat-card {
179
+ background: var(--bg-surface);
180
+ border: 1px solid var(--border-subtle);
181
+ border-radius: var(--radius-lg);
182
+ padding: 20px;
183
+ transition: all 0.2s ease;
184
+ }
185
+
186
+ .stat-card:hover {
187
+ border-color: var(--border-default);
188
+ background: var(--bg-elevated);
189
+ }
190
+
191
+ .badge {
192
+ display: inline-flex;
193
+ align-items: center;
194
+ padding: 3px 10px;
195
+ font-size: 12px;
196
+ font-weight: 500;
197
+ border-radius: 100px;
198
+ }
199
+
200
+ .badge-success {
201
+ background: rgba(74, 222, 128, 0.15);
202
+ color: var(--accent-green);
203
+ }
204
+
205
+ .badge-error {
206
+ background: rgba(248, 113, 113, 0.15);
207
+ color: var(--accent-red);
208
+ }
209
+
210
+ .badge-info {
211
+ background: rgba(34, 211, 238, 0.15);
212
+ color: var(--accent-cyan);
213
+ }
214
+
215
+ .badge-warning {
216
+ background: rgba(251, 191, 36, 0.15);
217
+ color: var(--accent-amber);
218
+ }
219
+
220
+ .table-header {
221
+ font-size: 11px;
222
+ font-weight: 600;
223
+ text-transform: uppercase;
224
+ letter-spacing: 0.05em;
225
+ color: var(--text-muted);
226
+ }
227
+
228
+ .table-row {
229
+ transition: background 0.15s ease;
230
+ }
231
+
232
+ .table-row:hover {
233
+ background: var(--bg-hover);
234
+ }
235
+
236
+ .gradient-text {
237
+ background: linear-gradient(135deg, var(--accent-pink) 0%, var(--accent-cyan) 100%);
238
+ -webkit-background-clip: text;
239
+ -webkit-text-fill-color: transparent;
240
+ background-clip: text;
241
+ }
242
+
243
+ .gradient-border {
244
+ position: relative;
245
+ }
246
+
247
+ .gradient-border::before {
248
+ content: '';
249
+ position: absolute;
250
+ inset: 0;
251
+ padding: 1px;
252
+ border-radius: inherit;
253
+ background: linear-gradient(135deg, var(--accent-pink), var(--accent-cyan));
254
+ -webkit-mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
255
+ mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
256
+ -webkit-mask-composite: xor;
257
+ mask-composite: exclude;
258
+ pointer-events: none;
259
+ }
260
+
261
+ .glow-pink {
262
+ box-shadow: 0 0 30px var(--accent-pink-glow);
263
+ }
264
+
265
+ .glow-cyan {
266
+ box-shadow: 0 0 30px var(--accent-cyan-glow);
267
+ }
268
+ }
269
+
270
+ @layer utilities {
271
+ .text-balance {
272
+ text-wrap: balance;
273
+ }
274
+
275
+ .animate-fade-in {
276
+ animation: fadeIn 0.3s ease-out;
277
+ }
278
+
279
+ .animate-slide-up {
280
+ animation: slideUp 0.3s ease-out;
281
+ }
282
+
283
+ @keyframes fadeIn {
284
+ from { opacity: 0; }
285
+ to { opacity: 1; }
286
+ }
287
+
288
+ @keyframes slideUp {
289
+ from {
290
+ opacity: 0;
291
+ transform: translateY(10px);
292
+ }
293
+ to {
294
+ opacity: 1;
295
+ transform: translateY(0);
296
+ }
297
+ }
298
+
299
+ .spin {
300
+ animation: spin 1s linear infinite;
301
+ }
302
+
303
+ @keyframes spin {
304
+ to { transform: rotate(360deg); }
305
+ }
306
+ }
@@ -0,0 +1,78 @@
1
+ /**
2
+ * Client-side type definitions.
3
+ *
4
+ * Shared shapes (aggregations, time-series, dashboard payloads) live in
5
+ * `../shared-types` and are re-exported here. The types declared inline below
6
+ * are deliberately client-only because:
7
+ * - `Usage` is redeclared locally so the client bundle avoids importing
8
+ * `@gajae-code/ai` (the server-side AI types package).
9
+ * - `MessageStats.stopReason` is widened from the server's `StopReason`
10
+ * enum to `string`, again to keep the client free of pi-ai types.
11
+ * - `TimeRange`, `OverviewStats`, `ModelDashboardStats`,
12
+ * `CostDashboardStats` are UI-only view shapes the server never produces.
13
+ */
14
+
15
+ import type {
16
+ AggregatedStats,
17
+ CostTimeSeriesPoint,
18
+ ModelPerformancePoint,
19
+ ModelStats,
20
+ ModelTimeSeriesPoint,
21
+ TimeSeriesPoint,
22
+ } from "../shared-types";
23
+
24
+ export * from "../shared-types";
25
+
26
+ export interface Usage {
27
+ input: number;
28
+ output: number;
29
+ cacheRead: number;
30
+ cacheWrite: number;
31
+ totalTokens: number;
32
+ premiumRequests?: number;
33
+ cost: {
34
+ input: number;
35
+ output: number;
36
+ cacheRead: number;
37
+ cacheWrite: number;
38
+ total: number;
39
+ };
40
+ }
41
+
42
+ export interface MessageStats {
43
+ id?: number;
44
+ sessionFile: string;
45
+ entryId: string;
46
+ folder: string;
47
+ model: string;
48
+ provider: string;
49
+ api: string;
50
+ timestamp: number;
51
+ duration: number | null;
52
+ ttft: number | null;
53
+ stopReason: string;
54
+ errorMessage: string | null;
55
+ usage: Usage;
56
+ }
57
+
58
+ export interface RequestDetails extends MessageStats {
59
+ messages: unknown[];
60
+ output: unknown;
61
+ }
62
+
63
+ export type TimeRange = "1h" | "24h" | "7d" | "30d" | "90d" | "all";
64
+
65
+ export interface OverviewStats {
66
+ overall: AggregatedStats;
67
+ timeSeries: TimeSeriesPoint[];
68
+ }
69
+
70
+ export interface ModelDashboardStats {
71
+ byModel: ModelStats[];
72
+ modelSeries: ModelTimeSeriesPoint[];
73
+ modelPerformanceSeries: ModelPerformancePoint[];
74
+ }
75
+
76
+ export interface CostDashboardStats {
77
+ costSeries: CostTimeSeriesPoint[];
78
+ }
@@ -0,0 +1,31 @@
1
+ import { useEffect, useState } from "react";
2
+
3
+ export type SystemTheme = "light" | "dark";
4
+
5
+ const DARK_SCHEME_QUERY = "(prefers-color-scheme: dark)";
6
+
7
+ function getSystemTheme(): SystemTheme {
8
+ if (typeof window === "undefined") {
9
+ return "light";
10
+ }
11
+
12
+ return window.matchMedia(DARK_SCHEME_QUERY).matches ? "dark" : "light";
13
+ }
14
+
15
+ export function useSystemTheme(): SystemTheme {
16
+ const [theme, setTheme] = useState<SystemTheme>(() => getSystemTheme());
17
+
18
+ useEffect(() => {
19
+ const media = window.matchMedia(DARK_SCHEME_QUERY);
20
+ const applyTheme = () => {
21
+ setTheme(media.matches ? "dark" : "light");
22
+ };
23
+
24
+ applyTheme();
25
+
26
+ media.addEventListener("change", applyTheme);
27
+ return () => media.removeEventListener("change", applyTheme);
28
+ }, []);
29
+
30
+ return theme;
31
+ }