@cryptiklemur/lattice 1.4.0 → 1.5.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/bun.lock +71 -0
- package/client/package.json +1 -0
- package/client/src/components/analytics/AnalyticsView.tsx +61 -0
- package/client/src/components/analytics/ChartCard.tsx +22 -0
- package/client/src/components/analytics/PeriodSelector.tsx +42 -0
- package/client/src/components/analytics/QuickStats.tsx +99 -0
- package/client/src/components/analytics/charts/CostAreaChart.tsx +83 -0
- package/client/src/components/analytics/charts/CostDistributionChart.tsx +62 -0
- package/client/src/components/analytics/charts/CostDonutChart.tsx +93 -0
- package/client/src/components/analytics/charts/CumulativeCostChart.tsx +62 -0
- package/client/src/components/analytics/charts/SessionBubbleChart.tsx +122 -0
- package/client/src/components/dashboard/DashboardView.tsx +5 -0
- package/client/src/components/sidebar/Sidebar.tsx +10 -2
- package/client/src/hooks/useAnalytics.ts +75 -0
- package/client/src/router.tsx +4 -0
- package/client/src/stores/analytics.ts +54 -0
- package/client/src/stores/sidebar.ts +8 -0
- package/client/vite.config.ts +1 -0
- package/package.json +1 -1
- package/server/src/analytics/engine.ts +491 -0
- package/server/src/daemon.ts +1 -0
- package/server/src/handlers/analytics.ts +34 -0
- package/server/src/project/session.ts +4 -4
- package/shared/src/analytics.ts +24 -0
- package/shared/src/index.ts +1 -0
- package/shared/src/messages.ts +30 -2
package/bun.lock
CHANGED
|
@@ -34,6 +34,7 @@
|
|
|
34
34
|
"react": "^19",
|
|
35
35
|
"react-dom": "^19",
|
|
36
36
|
"react-markdown": "^10.1.0",
|
|
37
|
+
"recharts": "2.15.3",
|
|
37
38
|
"remark-gfm": "^4.0.1",
|
|
38
39
|
"shiki": "^4.0.2",
|
|
39
40
|
"tailwindcss": "^4.2.2",
|
|
@@ -489,6 +490,24 @@
|
|
|
489
490
|
|
|
490
491
|
"@tybys/wasm-util": ["@tybys/wasm-util@0.10.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg=="],
|
|
491
492
|
|
|
493
|
+
"@types/d3-array": ["@types/d3-array@3.2.2", "", {}, "sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw=="],
|
|
494
|
+
|
|
495
|
+
"@types/d3-color": ["@types/d3-color@3.1.3", "", {}, "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A=="],
|
|
496
|
+
|
|
497
|
+
"@types/d3-ease": ["@types/d3-ease@3.0.2", "", {}, "sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA=="],
|
|
498
|
+
|
|
499
|
+
"@types/d3-interpolate": ["@types/d3-interpolate@3.0.4", "", { "dependencies": { "@types/d3-color": "*" } }, "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA=="],
|
|
500
|
+
|
|
501
|
+
"@types/d3-path": ["@types/d3-path@3.1.1", "", {}, "sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg=="],
|
|
502
|
+
|
|
503
|
+
"@types/d3-scale": ["@types/d3-scale@4.0.9", "", { "dependencies": { "@types/d3-time": "*" } }, "sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw=="],
|
|
504
|
+
|
|
505
|
+
"@types/d3-shape": ["@types/d3-shape@3.1.8", "", { "dependencies": { "@types/d3-path": "*" } }, "sha512-lae0iWfcDeR7qt7rA88BNiqdvPS5pFVPpo5OfjElwNaT2yyekbM0C9vK+yqBqEmHr6lDkRnYNoTBYlAgJa7a4w=="],
|
|
506
|
+
|
|
507
|
+
"@types/d3-time": ["@types/d3-time@3.0.4", "", {}, "sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g=="],
|
|
508
|
+
|
|
509
|
+
"@types/d3-timer": ["@types/d3-timer@3.0.2", "", {}, "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw=="],
|
|
510
|
+
|
|
492
511
|
"@types/debug": ["@types/debug@4.1.12", "", { "dependencies": { "@types/ms": "*" } }, "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ=="],
|
|
493
512
|
|
|
494
513
|
"@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="],
|
|
@@ -627,6 +646,8 @@
|
|
|
627
646
|
|
|
628
647
|
"cliui": ["cliui@9.0.1", "", { "dependencies": { "string-width": "^7.2.0", "strip-ansi": "^7.1.0", "wrap-ansi": "^9.0.0" } }, "sha512-k7ndgKhwoQveBL+/1tqGJYNz097I7WOvwbmmU2AR5+magtbjPWQTS1C5vzGkBC8Ym8UWRzfKUzUUqFLypY4Q+w=="],
|
|
629
648
|
|
|
649
|
+
"clsx": ["clsx@2.1.1", "", {}, "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA=="],
|
|
650
|
+
|
|
630
651
|
"color-convert": ["color-convert@1.9.3", "", { "dependencies": { "color-name": "1.1.3" } }, "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg=="],
|
|
631
652
|
|
|
632
653
|
"color-name": ["color-name@1.1.3", "", {}, "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw=="],
|
|
@@ -671,6 +692,28 @@
|
|
|
671
692
|
|
|
672
693
|
"csstype": ["csstype@3.2.3", "", {}, "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="],
|
|
673
694
|
|
|
695
|
+
"d3-array": ["d3-array@3.2.4", "", { "dependencies": { "internmap": "1 - 2" } }, "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg=="],
|
|
696
|
+
|
|
697
|
+
"d3-color": ["d3-color@3.1.0", "", {}, "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA=="],
|
|
698
|
+
|
|
699
|
+
"d3-ease": ["d3-ease@3.0.1", "", {}, "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w=="],
|
|
700
|
+
|
|
701
|
+
"d3-format": ["d3-format@3.1.2", "", {}, "sha512-AJDdYOdnyRDV5b6ArilzCPPwc1ejkHcoyFarqlPqT7zRYjhavcT3uSrqcMvsgh2CgoPbK3RCwyHaVyxYcP2Arg=="],
|
|
702
|
+
|
|
703
|
+
"d3-interpolate": ["d3-interpolate@3.0.1", "", { "dependencies": { "d3-color": "1 - 3" } }, "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g=="],
|
|
704
|
+
|
|
705
|
+
"d3-path": ["d3-path@3.1.0", "", {}, "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ=="],
|
|
706
|
+
|
|
707
|
+
"d3-scale": ["d3-scale@4.0.2", "", { "dependencies": { "d3-array": "2.10.0 - 3", "d3-format": "1 - 3", "d3-interpolate": "1.2.0 - 3", "d3-time": "2.1.1 - 3", "d3-time-format": "2 - 4" } }, "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ=="],
|
|
708
|
+
|
|
709
|
+
"d3-shape": ["d3-shape@3.2.0", "", { "dependencies": { "d3-path": "^3.1.0" } }, "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA=="],
|
|
710
|
+
|
|
711
|
+
"d3-time": ["d3-time@3.1.0", "", { "dependencies": { "d3-array": "2 - 3" } }, "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q=="],
|
|
712
|
+
|
|
713
|
+
"d3-time-format": ["d3-time-format@4.1.0", "", { "dependencies": { "d3-time": "1 - 3" } }, "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg=="],
|
|
714
|
+
|
|
715
|
+
"d3-timer": ["d3-timer@3.0.1", "", {}, "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA=="],
|
|
716
|
+
|
|
674
717
|
"daisyui": ["daisyui@5.5.19", "", {}, "sha512-pbFAkl1VCEh/MPCeclKL61I/MqRIFFhNU7yiXoDDRapXN4/qNCoMxeCCswyxEEhqL5eiTTfwHvucFtOE71C9sA=="],
|
|
675
718
|
|
|
676
719
|
"data-view-buffer": ["data-view-buffer@1.0.2", "", { "dependencies": { "call-bound": "^1.0.3", "es-errors": "^1.3.0", "is-data-view": "^1.0.2" } }, "sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ=="],
|
|
@@ -683,6 +726,8 @@
|
|
|
683
726
|
|
|
684
727
|
"decamelize": ["decamelize@1.2.0", "", {}, "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA=="],
|
|
685
728
|
|
|
729
|
+
"decimal.js-light": ["decimal.js-light@2.5.1", "", {}, "sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg=="],
|
|
730
|
+
|
|
686
731
|
"decode-named-character-reference": ["decode-named-character-reference@1.3.0", "", { "dependencies": { "character-entities": "^2.0.0" } }, "sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q=="],
|
|
687
732
|
|
|
688
733
|
"deep-extend": ["deep-extend@0.6.0", "", {}, "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA=="],
|
|
@@ -705,6 +750,8 @@
|
|
|
705
750
|
|
|
706
751
|
"dns-packet": ["dns-packet@5.6.1", "", { "dependencies": { "@leichtgewicht/ip-codec": "^2.0.1" } }, "sha512-l4gcSouhcgIKRvyy99RNVOgxXiicE+2jZoNmaNmZ6JXiGajBOJAesk1OBlJuM5k2c+eudGdLxDqXuPCKIj6kpw=="],
|
|
707
752
|
|
|
753
|
+
"dom-helpers": ["dom-helpers@5.2.1", "", { "dependencies": { "@babel/runtime": "^7.8.7", "csstype": "^3.0.2" } }, "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA=="],
|
|
754
|
+
|
|
708
755
|
"dot-prop": ["dot-prop@5.3.0", "", { "dependencies": { "is-obj": "^2.0.0" } }, "sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q=="],
|
|
709
756
|
|
|
710
757
|
"dunder-proto": ["dunder-proto@1.0.1", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" } }, "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A=="],
|
|
@@ -751,6 +798,8 @@
|
|
|
751
798
|
|
|
752
799
|
"esutils": ["esutils@2.0.3", "", {}, "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g=="],
|
|
753
800
|
|
|
801
|
+
"eventemitter3": ["eventemitter3@4.0.7", "", {}, "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw=="],
|
|
802
|
+
|
|
754
803
|
"execa": ["execa@9.6.1", "", { "dependencies": { "@sindresorhus/merge-streams": "^4.0.0", "cross-spawn": "^7.0.6", "figures": "^6.1.0", "get-stream": "^9.0.0", "human-signals": "^8.0.1", "is-plain-obj": "^4.1.0", "is-stream": "^4.0.1", "npm-run-path": "^6.0.0", "pretty-ms": "^9.2.0", "signal-exit": "^4.1.0", "strip-final-newline": "^4.0.0", "yoctocolors": "^2.1.1" } }, "sha512-9Be3ZoN4LmYR90tUoVu2te2BsbzHfhJyfEiAVfz7N5/zv+jduIfLrV2xdQXOHbaD6KgpGdO9PRPM1Y4Q9QkPkA=="],
|
|
755
804
|
|
|
756
805
|
"extend": ["extend@3.0.2", "", {}, "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g=="],
|
|
@@ -759,6 +808,8 @@
|
|
|
759
808
|
|
|
760
809
|
"fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="],
|
|
761
810
|
|
|
811
|
+
"fast-equals": ["fast-equals@5.4.0", "", {}, "sha512-jt2DW/aNFNwke7AUd+Z+e6pz39KO5rzdbbFCg2sGafS4mk13MI7Z8O5z9cADNn5lhGODIgLwug6TZO2ctf7kcw=="],
|
|
812
|
+
|
|
762
813
|
"fast-json-stable-stringify": ["fast-json-stable-stringify@2.1.0", "", {}, "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw=="],
|
|
763
814
|
|
|
764
815
|
"fast-uri": ["fast-uri@3.1.0", "", {}, "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA=="],
|
|
@@ -881,6 +932,8 @@
|
|
|
881
932
|
|
|
882
933
|
"internal-slot": ["internal-slot@1.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "hasown": "^2.0.2", "side-channel": "^1.1.0" } }, "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw=="],
|
|
883
934
|
|
|
935
|
+
"internmap": ["internmap@2.0.3", "", {}, "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg=="],
|
|
936
|
+
|
|
884
937
|
"into-stream": ["into-stream@7.0.0", "", { "dependencies": { "from2": "^2.3.0", "p-is-promise": "^3.0.0" } }, "sha512-2dYz766i9HprMBasCMvHMuazJ7u4WzhJwo5kb3iPSiW/iRYV6uPari3zHoqZlnuaR7V1bEiNMxikhp37rdBXbw=="],
|
|
885
938
|
|
|
886
939
|
"is-alphabetical": ["is-alphabetical@2.0.1", "", {}, "sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ=="],
|
|
@@ -1045,6 +1098,8 @@
|
|
|
1045
1098
|
|
|
1046
1099
|
"longest-streak": ["longest-streak@3.1.0", "", {}, "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g=="],
|
|
1047
1100
|
|
|
1101
|
+
"loose-envify": ["loose-envify@1.4.0", "", { "dependencies": { "js-tokens": "^3.0.0 || ^4.0.0" }, "bin": { "loose-envify": "cli.js" } }, "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q=="],
|
|
1102
|
+
|
|
1048
1103
|
"lru-cache": ["lru-cache@11.2.7", "", {}, "sha512-aY/R+aEsRelme17KGQa/1ZSIpLpNYYrhcrepKTZgE+W3WM16YMCaPwOHLHsmopZHELU0Ojin1lPVxKR0MihncA=="],
|
|
1049
1104
|
|
|
1050
1105
|
"lucide-react": ["lucide-react@0.577.0", "", { "peerDependencies": { "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-4LjoFv2eEPwYDPg/CUdBJQSDfPyzXCRrVW1X7jrx/trgxnxkHFjnVZINbzvzxjN70dxychOfg+FTYwBiS3pQ5A=="],
|
|
@@ -1273,6 +1328,8 @@
|
|
|
1273
1328
|
|
|
1274
1329
|
"process-nextick-args": ["process-nextick-args@2.0.1", "", {}, "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag=="],
|
|
1275
1330
|
|
|
1331
|
+
"prop-types": ["prop-types@15.8.1", "", { "dependencies": { "loose-envify": "^1.4.0", "object-assign": "^4.1.1", "react-is": "^16.13.1" } }, "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg=="],
|
|
1332
|
+
|
|
1276
1333
|
"property-information": ["property-information@7.1.0", "", {}, "sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ=="],
|
|
1277
1334
|
|
|
1278
1335
|
"proto-list": ["proto-list@1.2.4", "", {}, "sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA=="],
|
|
@@ -1289,14 +1346,24 @@
|
|
|
1289
1346
|
|
|
1290
1347
|
"react-dom": ["react-dom@19.2.4", "", { "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { "react": "^19.2.4" } }, "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ=="],
|
|
1291
1348
|
|
|
1349
|
+
"react-is": ["react-is@18.3.1", "", {}, "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg=="],
|
|
1350
|
+
|
|
1292
1351
|
"react-markdown": ["react-markdown@10.1.0", "", { "dependencies": { "@types/hast": "^3.0.0", "@types/mdast": "^4.0.0", "devlop": "^1.0.0", "hast-util-to-jsx-runtime": "^2.0.0", "html-url-attributes": "^3.0.0", "mdast-util-to-hast": "^13.0.0", "remark-parse": "^11.0.0", "remark-rehype": "^11.0.0", "unified": "^11.0.0", "unist-util-visit": "^5.0.0", "vfile": "^6.0.0" }, "peerDependencies": { "@types/react": ">=18", "react": ">=18" } }, "sha512-qKxVopLT/TyA6BX3Ue5NwabOsAzm0Q7kAPwq6L+wWDwisYs7R8vZ0nRXqq6rkueboxpkjvLGU9fWifiX/ZZFxQ=="],
|
|
1293
1352
|
|
|
1353
|
+
"react-smooth": ["react-smooth@4.0.4", "", { "dependencies": { "fast-equals": "^5.0.1", "prop-types": "^15.8.1", "react-transition-group": "^4.4.5" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-gnGKTpYwqL0Iii09gHobNolvX4Kiq4PKx6eWBCYYix+8cdw+cGo3do906l1NBPKkSWx1DghC1dlWG9L2uGd61Q=="],
|
|
1354
|
+
|
|
1355
|
+
"react-transition-group": ["react-transition-group@4.4.5", "", { "dependencies": { "@babel/runtime": "^7.5.5", "dom-helpers": "^5.0.1", "loose-envify": "^1.4.0", "prop-types": "^15.6.2" }, "peerDependencies": { "react": ">=16.6.0", "react-dom": ">=16.6.0" } }, "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g=="],
|
|
1356
|
+
|
|
1294
1357
|
"read-package-up": ["read-package-up@11.0.0", "", { "dependencies": { "find-up-simple": "^1.0.0", "read-pkg": "^9.0.0", "type-fest": "^4.6.0" } }, "sha512-MbgfoNPANMdb4oRBNg5eqLbB2t2r+o5Ua1pNt8BqGp4I0FJZhuVSOj3PaBPni4azWuSzEdNn2evevzVmEk1ohQ=="],
|
|
1295
1358
|
|
|
1296
1359
|
"read-pkg": ["read-pkg@10.1.0", "", { "dependencies": { "@types/normalize-package-data": "^2.4.4", "normalize-package-data": "^8.0.0", "parse-json": "^8.3.0", "type-fest": "^5.4.4", "unicorn-magic": "^0.4.0" } }, "sha512-I8g2lArQiP78ll51UeMZojewtYgIRCKCWqZEgOO8c/uefTI+XDXvCSXu3+YNUaTNvZzobrL5+SqHjBrByRRTdg=="],
|
|
1297
1360
|
|
|
1298
1361
|
"readable-stream": ["readable-stream@2.3.8", "", { "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", "isarray": "~1.0.0", "process-nextick-args": "~2.0.0", "safe-buffer": "~5.1.1", "string_decoder": "~1.1.1", "util-deprecate": "~1.0.1" } }, "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA=="],
|
|
1299
1362
|
|
|
1363
|
+
"recharts": ["recharts@2.15.3", "", { "dependencies": { "clsx": "^2.0.0", "eventemitter3": "^4.0.1", "lodash": "^4.17.21", "react-is": "^18.3.1", "react-smooth": "^4.0.4", "recharts-scale": "^0.4.4", "tiny-invariant": "^1.3.1", "victory-vendor": "^36.6.8" }, "peerDependencies": { "react": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-EdOPzTwcFSuqtvkDoaM5ws/Km1+WTAO2eizL7rqiG0V2UVhTnz0m7J2i0CjVPUCdEkZImaWvXLbZDS2H5t6GFQ=="],
|
|
1364
|
+
|
|
1365
|
+
"recharts-scale": ["recharts-scale@0.4.5", "", { "dependencies": { "decimal.js-light": "^2.4.1" } }, "sha512-kivNFO+0OcUNu7jQquLXAxz1FIwZj8nrj+YkOKc5694NbjCvcT6aSZiIzNzd2Kul4o4rTto8QVR9lMNtxD4G1w=="],
|
|
1366
|
+
|
|
1300
1367
|
"reflect.getprototypeof": ["reflect.getprototypeof@1.0.10", "", { "dependencies": { "call-bind": "^1.0.8", "define-properties": "^1.2.1", "es-abstract": "^1.23.9", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0", "get-intrinsic": "^1.2.7", "get-proto": "^1.0.1", "which-builtin-type": "^1.2.1" } }, "sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw=="],
|
|
1301
1368
|
|
|
1302
1369
|
"regenerate": ["regenerate@1.4.2", "", {}, "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A=="],
|
|
@@ -1565,6 +1632,8 @@
|
|
|
1565
1632
|
|
|
1566
1633
|
"vfile-message": ["vfile-message@4.0.3", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-stringify-position": "^4.0.0" } }, "sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw=="],
|
|
1567
1634
|
|
|
1635
|
+
"victory-vendor": ["victory-vendor@36.9.2", "", { "dependencies": { "@types/d3-array": "^3.0.3", "@types/d3-ease": "^3.0.0", "@types/d3-interpolate": "^3.0.1", "@types/d3-scale": "^4.0.2", "@types/d3-shape": "^3.1.0", "@types/d3-time": "^3.0.0", "@types/d3-timer": "^3.0.0", "d3-array": "^3.1.6", "d3-ease": "^3.0.1", "d3-interpolate": "^3.0.1", "d3-scale": "^4.0.2", "d3-shape": "^3.1.0", "d3-time": "^3.0.0", "d3-timer": "^3.0.1" } }, "sha512-PnpQQMuxlwYdocC8fIJqVXvkeViHYzotI+NJrCuav0ZYFoq912ZHBk3mCeuj+5/VpodOjPe1z0Fk2ihgzlXqjQ=="],
|
|
1636
|
+
|
|
1568
1637
|
"vite": ["vite@8.0.1", "", { "dependencies": { "lightningcss": "^1.32.0", "picomatch": "^4.0.3", "postcss": "^8.5.8", "rolldown": "1.0.0-rc.10", "tinyglobby": "^0.2.15" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "@vitejs/devtools": "^0.1.0", "esbuild": "^0.27.0", "jiti": ">=1.21.0", "less": "^4.0.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "@vitejs/devtools", "esbuild", "jiti", "less", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-wt+Z2qIhfFt85uiyRt5LPU4oVEJBXj8hZNWKeqFG4gRG/0RaRGJ7njQCwzFVjO+v4+Ipmf5CY7VdmZRAYYBPHw=="],
|
|
1569
1638
|
|
|
1570
1639
|
"vite-plugin-pwa": ["vite-plugin-pwa@1.2.0", "", { "dependencies": { "debug": "^4.3.6", "pretty-bytes": "^6.1.1", "tinyglobby": "^0.2.10", "workbox-build": "^7.4.0", "workbox-window": "^7.4.0" }, "peerDependencies": { "@vite-pwa/assets-generator": "^1.0.0", "vite": "^3.1.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" }, "optionalPeers": ["@vite-pwa/assets-generator"] }, "sha512-a2xld+SJshT9Lgcv8Ji4+srFJL4k/1bVbd1x06JIkvecpQkwkvCncD1+gSzcdm3s+owWLpMJerG3aN5jupJEVw=="],
|
|
@@ -1999,6 +2068,8 @@
|
|
|
1999
2068
|
|
|
2000
2069
|
"pkg-conf/find-up": ["find-up@2.1.0", "", { "dependencies": { "locate-path": "^2.0.0" } }, "sha512-NWzkk0jSJtTt08+FBFMvXoeZnOJD+jTtsRmBYbAIzJdX6l7dLgR7CTubCM5/eDdPUBvLCeVasP1brfVR/9/EZQ=="],
|
|
2001
2070
|
|
|
2071
|
+
"prop-types/react-is": ["react-is@16.13.1", "", {}, "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="],
|
|
2072
|
+
|
|
2002
2073
|
"qrcode/yargs": ["yargs@15.4.1", "", { "dependencies": { "cliui": "^6.0.0", "decamelize": "^1.2.0", "find-up": "^4.1.0", "get-caller-file": "^2.0.1", "require-directory": "^2.1.1", "require-main-filename": "^2.0.0", "set-blocking": "^2.0.0", "string-width": "^4.2.0", "which-module": "^2.0.0", "y18n": "^4.0.0", "yargs-parser": "^18.1.2" } }, "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A=="],
|
|
2003
2074
|
|
|
2004
2075
|
"read-package-up/read-pkg": ["read-pkg@9.0.1", "", { "dependencies": { "@types/normalize-package-data": "^2.4.3", "normalize-package-data": "^6.0.0", "parse-json": "^8.0.0", "type-fest": "^4.6.0", "unicorn-magic": "^0.1.0" } }, "sha512-9viLL4/n1BJUCT1NXVTdS1jtm80yDEgR5T4yCelII49Mbj0v1rZdKqj7zCiYdbB0CuCgdrvHcNogAKTFPBocFA=="],
|
package/client/package.json
CHANGED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { useAnalytics } from "../../hooks/useAnalytics";
|
|
2
|
+
import { PeriodSelector } from "./PeriodSelector";
|
|
3
|
+
import { ChartCard } from "./ChartCard";
|
|
4
|
+
import { CostAreaChart } from "./charts/CostAreaChart";
|
|
5
|
+
import { CumulativeCostChart } from "./charts/CumulativeCostChart";
|
|
6
|
+
import { CostDonutChart } from "./charts/CostDonutChart";
|
|
7
|
+
import { CostDistributionChart } from "./charts/CostDistributionChart";
|
|
8
|
+
import { SessionBubbleChart } from "./charts/SessionBubbleChart";
|
|
9
|
+
|
|
10
|
+
export function AnalyticsView() {
|
|
11
|
+
var analytics = useAnalytics();
|
|
12
|
+
|
|
13
|
+
return (
|
|
14
|
+
<div className="flex flex-col h-full overflow-hidden bg-base-100 bg-lattice-grid">
|
|
15
|
+
<div className="flex items-center justify-between px-6 py-4 border-b border-base-300 flex-shrink-0">
|
|
16
|
+
<h1 className="text-[16px] font-mono font-bold text-base-content">Analytics</h1>
|
|
17
|
+
<PeriodSelector value={analytics.period} onChange={analytics.setPeriod} />
|
|
18
|
+
</div>
|
|
19
|
+
|
|
20
|
+
<div className="flex-1 overflow-y-auto px-6 py-4">
|
|
21
|
+
{analytics.loading && !analytics.data && (
|
|
22
|
+
<div className="text-center text-base-content/30 py-20 font-mono text-[13px]">Loading analytics...</div>
|
|
23
|
+
)}
|
|
24
|
+
|
|
25
|
+
{analytics.error && (
|
|
26
|
+
<div className="text-center text-error/60 py-20 font-mono text-[13px]">{analytics.error}</div>
|
|
27
|
+
)}
|
|
28
|
+
|
|
29
|
+
{analytics.data && (
|
|
30
|
+
<div className="flex flex-col gap-4 max-w-[1200px] mx-auto pb-8">
|
|
31
|
+
<ChartCard title="Cost Over Time">
|
|
32
|
+
<CostAreaChart data={analytics.data.costOverTime} />
|
|
33
|
+
</ChartCard>
|
|
34
|
+
|
|
35
|
+
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
36
|
+
<ChartCard title="Cost Breakdown">
|
|
37
|
+
<CostDonutChart modelUsage={analytics.data.modelUsage} totalCost={analytics.data.totalCost} />
|
|
38
|
+
</ChartCard>
|
|
39
|
+
<ChartCard title="Cumulative Cost">
|
|
40
|
+
<CumulativeCostChart data={analytics.data.cumulativeCost} />
|
|
41
|
+
</ChartCard>
|
|
42
|
+
</div>
|
|
43
|
+
|
|
44
|
+
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
45
|
+
<ChartCard title="Cost Distribution">
|
|
46
|
+
<CostDistributionChart data={analytics.data.costDistribution} />
|
|
47
|
+
</ChartCard>
|
|
48
|
+
<ChartCard title="Session Costs">
|
|
49
|
+
<SessionBubbleChart data={analytics.data.sessionBubbles} />
|
|
50
|
+
</ChartCard>
|
|
51
|
+
</div>
|
|
52
|
+
</div>
|
|
53
|
+
)}
|
|
54
|
+
|
|
55
|
+
{!analytics.loading && !analytics.error && !analytics.data && (
|
|
56
|
+
<div className="text-center text-base-content/30 py-20 font-mono text-[13px]">No analytics data yet</div>
|
|
57
|
+
)}
|
|
58
|
+
</div>
|
|
59
|
+
</div>
|
|
60
|
+
);
|
|
61
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
|
|
3
|
+
interface ChartCardProps {
|
|
4
|
+
title: string;
|
|
5
|
+
children: ReactNode;
|
|
6
|
+
className?: string;
|
|
7
|
+
action?: ReactNode;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function ChartCard({ title, children, className, action }: ChartCardProps) {
|
|
11
|
+
return (
|
|
12
|
+
<div className={["rounded-xl border border-base-content/8 bg-base-300/50 p-4", className].filter(Boolean).join(" ")}>
|
|
13
|
+
<div className="flex items-center justify-between mb-4">
|
|
14
|
+
<span className="text-[10px] font-mono font-bold uppercase tracking-widest text-base-content/35">
|
|
15
|
+
{title}
|
|
16
|
+
</span>
|
|
17
|
+
{action && <div>{action}</div>}
|
|
18
|
+
</div>
|
|
19
|
+
{children}
|
|
20
|
+
</div>
|
|
21
|
+
);
|
|
22
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
type Period = "24h" | "7d" | "30d" | "90d" | "all";
|
|
2
|
+
|
|
3
|
+
var PERIODS: Array<{ value: Period; label: string }> = [
|
|
4
|
+
{ value: "24h", label: "24h" },
|
|
5
|
+
{ value: "7d", label: "7d" },
|
|
6
|
+
{ value: "30d", label: "30d" },
|
|
7
|
+
{ value: "90d", label: "90d" },
|
|
8
|
+
{ value: "all", label: "All" },
|
|
9
|
+
];
|
|
10
|
+
|
|
11
|
+
interface PeriodSelectorProps {
|
|
12
|
+
value: Period;
|
|
13
|
+
onChange: (period: Period) => void;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function PeriodSelector({ value, onChange }: PeriodSelectorProps) {
|
|
17
|
+
return (
|
|
18
|
+
<div role="radiogroup" aria-label="Time period" className="flex items-center gap-1">
|
|
19
|
+
{PERIODS.map(function (period) {
|
|
20
|
+
var isActive = period.value === value;
|
|
21
|
+
return (
|
|
22
|
+
<button
|
|
23
|
+
key={period.value}
|
|
24
|
+
role="radio"
|
|
25
|
+
aria-checked={isActive}
|
|
26
|
+
onClick={function () { onChange(period.value); }}
|
|
27
|
+
className={[
|
|
28
|
+
"px-2.5 py-1 rounded-md border text-[10px] font-mono font-bold uppercase tracking-widest transition-colors",
|
|
29
|
+
isActive
|
|
30
|
+
? "bg-primary/15 text-primary border-primary/30"
|
|
31
|
+
: "text-base-content/35 border-base-content/8 hover:text-base-content/60 hover:border-base-content/20",
|
|
32
|
+
].join(" ")}
|
|
33
|
+
>
|
|
34
|
+
{period.label}
|
|
35
|
+
</button>
|
|
36
|
+
);
|
|
37
|
+
})}
|
|
38
|
+
</div>
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export type { Period };
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { LineChart, Line, ResponsiveContainer } from "recharts";
|
|
2
|
+
import { useAnalytics } from "../../hooks/useAnalytics";
|
|
3
|
+
|
|
4
|
+
function formatTokens(n: number): string {
|
|
5
|
+
if (n >= 1_000_000) return (n / 1_000_000).toFixed(1) + "M";
|
|
6
|
+
if (n >= 1_000) return Math.round(n / 1_000) + "k";
|
|
7
|
+
return String(n);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
interface SparklineProps {
|
|
11
|
+
data: Array<{ v: number }>;
|
|
12
|
+
stroke: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function Sparkline({ data, stroke }: SparklineProps) {
|
|
16
|
+
return (
|
|
17
|
+
<ResponsiveContainer width={60} height={20}>
|
|
18
|
+
<LineChart data={data} margin={{ top: 2, right: 2, left: 2, bottom: 2 }}>
|
|
19
|
+
<Line
|
|
20
|
+
type="monotone"
|
|
21
|
+
dataKey="v"
|
|
22
|
+
stroke={stroke}
|
|
23
|
+
strokeWidth={1.5}
|
|
24
|
+
dot={false}
|
|
25
|
+
isAnimationActive={false}
|
|
26
|
+
/>
|
|
27
|
+
</LineChart>
|
|
28
|
+
</ResponsiveContainer>
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function QuickStats() {
|
|
33
|
+
var analytics = useAnalytics();
|
|
34
|
+
|
|
35
|
+
if (!analytics.data) {
|
|
36
|
+
return (
|
|
37
|
+
<div className="grid grid-cols-2 sm:grid-cols-4 gap-3">
|
|
38
|
+
{[0, 1, 2, 3].map(function (i) {
|
|
39
|
+
return (
|
|
40
|
+
<div key={i} className="bg-base-content/[0.03] border border-base-content/8 rounded-xl p-3.5 animate-pulse">
|
|
41
|
+
<div className="h-2.5 w-16 bg-base-content/10 rounded mb-3" />
|
|
42
|
+
<div className="h-6 w-12 bg-base-content/10 rounded" />
|
|
43
|
+
</div>
|
|
44
|
+
);
|
|
45
|
+
})}
|
|
46
|
+
</div>
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
var d = analytics.data;
|
|
51
|
+
|
|
52
|
+
var costSparkData = d.costOverTime.slice(-7).map(function (e) { return { v: e.total }; });
|
|
53
|
+
var sessionsSparkData = d.sessionsOverTime.slice(-7).map(function (e) { return { v: e.count }; });
|
|
54
|
+
var tokensSparkData = d.tokensOverTime.slice(-7).map(function (e) { return { v: e.input + e.output }; });
|
|
55
|
+
|
|
56
|
+
var totalTokens = d.totalTokens.input + d.totalTokens.output;
|
|
57
|
+
var cacheHitPct = Math.round(d.cacheHitRate * 100);
|
|
58
|
+
|
|
59
|
+
return (
|
|
60
|
+
<div className="grid grid-cols-2 sm:grid-cols-4 gap-3">
|
|
61
|
+
<div className="bg-base-content/[0.03] border border-base-content/8 rounded-xl p-3.5">
|
|
62
|
+
<div className="flex items-center justify-between mb-1">
|
|
63
|
+
<span className="text-[10px] font-mono font-semibold uppercase tracking-wider text-base-content/35">Cost</span>
|
|
64
|
+
{costSparkData.length > 1 && <Sparkline data={costSparkData} stroke="oklch(55% 0.25 280)" />}
|
|
65
|
+
</div>
|
|
66
|
+
<div className="text-[22px] font-mono text-base-content/85">${d.totalCost.toFixed(2)}</div>
|
|
67
|
+
</div>
|
|
68
|
+
|
|
69
|
+
<div className="bg-base-content/[0.03] border border-base-content/8 rounded-xl p-3.5">
|
|
70
|
+
<div className="flex items-center justify-between mb-1">
|
|
71
|
+
<span className="text-[10px] font-mono font-semibold uppercase tracking-wider text-base-content/35">Sessions</span>
|
|
72
|
+
{sessionsSparkData.length > 1 && <Sparkline data={sessionsSparkData} stroke="#22c55e" />}
|
|
73
|
+
</div>
|
|
74
|
+
<div className="text-[22px] font-mono text-base-content/85">{d.totalSessions}</div>
|
|
75
|
+
</div>
|
|
76
|
+
|
|
77
|
+
<div className="bg-base-content/[0.03] border border-base-content/8 rounded-xl p-3.5">
|
|
78
|
+
<div className="flex items-center justify-between mb-1">
|
|
79
|
+
<span className="text-[10px] font-mono font-semibold uppercase tracking-wider text-base-content/35">Tokens</span>
|
|
80
|
+
{tokensSparkData.length > 1 && <Sparkline data={tokensSparkData} stroke="#f59e0b" />}
|
|
81
|
+
</div>
|
|
82
|
+
<div className="text-[22px] font-mono text-base-content/85">{formatTokens(totalTokens)}</div>
|
|
83
|
+
</div>
|
|
84
|
+
|
|
85
|
+
<div className="bg-base-content/[0.03] border border-base-content/8 rounded-xl p-3.5">
|
|
86
|
+
<div className="mb-1">
|
|
87
|
+
<span className="text-[10px] font-mono font-semibold uppercase tracking-wider text-base-content/35">Cache Hit</span>
|
|
88
|
+
</div>
|
|
89
|
+
<div className="text-[22px] font-mono text-base-content/85 mb-2">{cacheHitPct}%</div>
|
|
90
|
+
<div className="w-full h-1 rounded-full bg-base-content/10 overflow-hidden">
|
|
91
|
+
<div
|
|
92
|
+
className="h-full rounded-full bg-primary transition-all duration-300"
|
|
93
|
+
style={{ width: cacheHitPct + "%" }}
|
|
94
|
+
/>
|
|
95
|
+
</div>
|
|
96
|
+
</div>
|
|
97
|
+
</div>
|
|
98
|
+
);
|
|
99
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import {
|
|
2
|
+
AreaChart,
|
|
3
|
+
Area,
|
|
4
|
+
XAxis,
|
|
5
|
+
YAxis,
|
|
6
|
+
CartesianGrid,
|
|
7
|
+
Tooltip,
|
|
8
|
+
ResponsiveContainer,
|
|
9
|
+
} from "recharts";
|
|
10
|
+
|
|
11
|
+
var TICK_STYLE = {
|
|
12
|
+
fontSize: 10,
|
|
13
|
+
fontFamily: "var(--font-mono)",
|
|
14
|
+
fill: "oklch(0.9 0.02 280 / 0.3)",
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
var GRID_COLOR = "oklch(0.9 0.02 280 / 0.06)";
|
|
18
|
+
|
|
19
|
+
interface CostAreaDatum {
|
|
20
|
+
date: string;
|
|
21
|
+
total: number;
|
|
22
|
+
opus: number;
|
|
23
|
+
sonnet: number;
|
|
24
|
+
haiku: number;
|
|
25
|
+
other: number;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
interface CostAreaChartProps {
|
|
29
|
+
data: CostAreaDatum[];
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function CustomTooltip({ active, payload, label }: { active?: boolean; payload?: Array<{ name: string; value: number; color: string }>; label?: string }) {
|
|
33
|
+
if (!active || !payload || payload.length === 0) return null;
|
|
34
|
+
return (
|
|
35
|
+
<div className="rounded-lg border border-base-content/8 bg-base-200 px-3 py-2 shadow-lg">
|
|
36
|
+
<p className="text-[10px] font-mono text-base-content/50 mb-1">{label}</p>
|
|
37
|
+
{payload.map(function (entry) {
|
|
38
|
+
return (
|
|
39
|
+
<div key={entry.name} className="flex items-center gap-2 text-[11px] font-mono">
|
|
40
|
+
<span className="inline-block w-2 h-2 rounded-full" style={{ background: entry.color }} />
|
|
41
|
+
<span className="text-base-content/60 capitalize">{entry.name}</span>
|
|
42
|
+
<span className="text-base-content ml-auto pl-4">${entry.value.toFixed(4)}</span>
|
|
43
|
+
</div>
|
|
44
|
+
);
|
|
45
|
+
})}
|
|
46
|
+
</div>
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function CostAreaChart({ data }: CostAreaChartProps) {
|
|
51
|
+
return (
|
|
52
|
+
<ResponsiveContainer width="100%" height={200}>
|
|
53
|
+
<AreaChart data={data} margin={{ top: 4, right: 4, left: -20, bottom: 0 }}>
|
|
54
|
+
<defs>
|
|
55
|
+
<linearGradient id="opusGrad" x1="0" y1="0" x2="0" y2="1">
|
|
56
|
+
<stop offset="5%" stopColor="#a855f7" stopOpacity={0.4} />
|
|
57
|
+
<stop offset="95%" stopColor="#a855f7" stopOpacity={0.05} />
|
|
58
|
+
</linearGradient>
|
|
59
|
+
<linearGradient id="sonnetGrad" x1="0" y1="0" x2="0" y2="1">
|
|
60
|
+
<stop offset="5%" stopColor="oklch(55% 0.25 280)" stopOpacity={0.4} />
|
|
61
|
+
<stop offset="95%" stopColor="oklch(55% 0.25 280)" stopOpacity={0.05} />
|
|
62
|
+
</linearGradient>
|
|
63
|
+
<linearGradient id="haikuGrad" x1="0" y1="0" x2="0" y2="1">
|
|
64
|
+
<stop offset="5%" stopColor="#22c55e" stopOpacity={0.4} />
|
|
65
|
+
<stop offset="95%" stopColor="#22c55e" stopOpacity={0.05} />
|
|
66
|
+
</linearGradient>
|
|
67
|
+
<linearGradient id="otherGrad" x1="0" y1="0" x2="0" y2="1">
|
|
68
|
+
<stop offset="5%" stopColor="#f59e0b" stopOpacity={0.4} />
|
|
69
|
+
<stop offset="95%" stopColor="#f59e0b" stopOpacity={0.05} />
|
|
70
|
+
</linearGradient>
|
|
71
|
+
</defs>
|
|
72
|
+
<CartesianGrid strokeDasharray="3 3" stroke={GRID_COLOR} vertical={false} />
|
|
73
|
+
<XAxis dataKey="date" tick={TICK_STYLE} axisLine={false} tickLine={false} />
|
|
74
|
+
<YAxis tick={TICK_STYLE} axisLine={false} tickLine={false} tickFormatter={function (v) { return "$" + v.toFixed(2); }} />
|
|
75
|
+
<Tooltip content={<CustomTooltip />} />
|
|
76
|
+
<Area type="monotone" dataKey="opus" stackId="1" stroke="#a855f7" fill="url(#opusGrad)" strokeWidth={1.5} />
|
|
77
|
+
<Area type="monotone" dataKey="sonnet" stackId="1" stroke="oklch(55% 0.25 280)" fill="url(#sonnetGrad)" strokeWidth={1.5} />
|
|
78
|
+
<Area type="monotone" dataKey="haiku" stackId="1" stroke="#22c55e" fill="url(#haikuGrad)" strokeWidth={1.5} />
|
|
79
|
+
<Area type="monotone" dataKey="other" stackId="1" stroke="#f59e0b" fill="url(#otherGrad)" strokeWidth={1.5} />
|
|
80
|
+
</AreaChart>
|
|
81
|
+
</ResponsiveContainer>
|
|
82
|
+
);
|
|
83
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import {
|
|
2
|
+
AreaChart,
|
|
3
|
+
Area,
|
|
4
|
+
XAxis,
|
|
5
|
+
YAxis,
|
|
6
|
+
CartesianGrid,
|
|
7
|
+
Tooltip,
|
|
8
|
+
ResponsiveContainer,
|
|
9
|
+
} from "recharts";
|
|
10
|
+
|
|
11
|
+
var TICK_STYLE = {
|
|
12
|
+
fontSize: 10,
|
|
13
|
+
fontFamily: "var(--font-mono)",
|
|
14
|
+
fill: "oklch(0.9 0.02 280 / 0.3)",
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
var GRID_COLOR = "oklch(0.9 0.02 280 / 0.06)";
|
|
18
|
+
|
|
19
|
+
interface DistributionDatum {
|
|
20
|
+
bucket: string;
|
|
21
|
+
count: number;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
interface CostDistributionChartProps {
|
|
25
|
+
data: DistributionDatum[];
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function CustomTooltip({ active, payload, label }: { active?: boolean; payload?: Array<{ value: number }>; label?: string }) {
|
|
29
|
+
if (!active || !payload || payload.length === 0) return null;
|
|
30
|
+
return (
|
|
31
|
+
<div className="rounded-lg border border-base-content/8 bg-base-200 px-3 py-2 shadow-lg">
|
|
32
|
+
<p className="text-[10px] font-mono text-base-content/50 mb-1">{label}</p>
|
|
33
|
+
<p className="text-[11px] font-mono text-base-content">{payload[0].value} sessions</p>
|
|
34
|
+
</div>
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function CostDistributionChart({ data }: CostDistributionChartProps) {
|
|
39
|
+
return (
|
|
40
|
+
<ResponsiveContainer width="100%" height={200}>
|
|
41
|
+
<AreaChart data={data} margin={{ top: 4, right: 4, left: -20, bottom: 0 }}>
|
|
42
|
+
<defs>
|
|
43
|
+
<linearGradient id="distGrad" x1="0" y1="0" x2="0" y2="1">
|
|
44
|
+
<stop offset="5%" stopColor="oklch(55% 0.25 280)" stopOpacity={0.35} />
|
|
45
|
+
<stop offset="95%" stopColor="oklch(55% 0.25 280)" stopOpacity={0.02} />
|
|
46
|
+
</linearGradient>
|
|
47
|
+
</defs>
|
|
48
|
+
<CartesianGrid strokeDasharray="3 3" stroke={GRID_COLOR} vertical={false} />
|
|
49
|
+
<XAxis dataKey="bucket" tick={TICK_STYLE} axisLine={false} tickLine={false} />
|
|
50
|
+
<YAxis tick={TICK_STYLE} axisLine={false} tickLine={false} allowDecimals={false} />
|
|
51
|
+
<Tooltip content={<CustomTooltip />} />
|
|
52
|
+
<Area
|
|
53
|
+
type="monotone"
|
|
54
|
+
dataKey="count"
|
|
55
|
+
stroke="oklch(55% 0.25 280)"
|
|
56
|
+
fill="url(#distGrad)"
|
|
57
|
+
strokeWidth={2}
|
|
58
|
+
/>
|
|
59
|
+
</AreaChart>
|
|
60
|
+
</ResponsiveContainer>
|
|
61
|
+
);
|
|
62
|
+
}
|