claude-session-dashboard 0.1.3 → 0.3.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.
Files changed (44) hide show
  1. package/README.md +156 -14
  2. package/dist/client/assets/_dashboard-I7m6D7BE.js +1 -0
  3. package/dist/client/assets/_sessionId-DEliIff6.js +12 -0
  4. package/dist/client/assets/app-D7yorIIh.css +1 -0
  5. package/dist/client/assets/{createServerFn-Le0d8Pjz.js → createServerFn-Bn6_ISOt.js} +1 -1
  6. package/dist/client/assets/format-Bsprb3az.js +1 -0
  7. package/dist/client/assets/index-BkqRvnEf.js +1 -0
  8. package/dist/client/assets/{main-CzD8HjLq.js → main-CfJIADCp.js} +7 -7
  9. package/dist/client/assets/sessions.queries-CrJg4dYU.js +1 -0
  10. package/dist/client/assets/settings-C4_lsEzl.js +1 -0
  11. package/dist/client/assets/{settings.types-B4841OLF.js → settings.types-9Qf5WcRY.js} +1 -1
  12. package/dist/client/assets/stats-_r1gmaTe.js +4 -0
  13. package/dist/client/assets/useSessionCost-DPZ-ubM1.js +65 -0
  14. package/dist/client/favicon.svg +3 -0
  15. package/dist/server/assets/_dashboard-TUzgwLqB.js +112 -0
  16. package/dist/server/assets/{_sessionId-BwZK4Ezz.js → _sessionId-C-XZIPqn.js} +57 -35
  17. package/dist/server/assets/_tanstack-start-manifest_v-B51mSkGz.js +4 -0
  18. package/dist/server/assets/{claude-path-CkuljM34.js → claude-path-BdwflgZ1.js} +9 -3
  19. package/dist/server/assets/{format-CGmJnuhZ.js → format-DIZHV7IJ.js} +3 -3
  20. package/dist/server/assets/{index-D4VWrt2z.js → index-CKfH7HpA.js} +28 -60
  21. package/dist/server/assets/project-analytics.server-BkWSd6a8.js +61 -0
  22. package/dist/server/assets/{router-xTSe9UH_.js → router-Cb_hBXHI.js} +62 -31
  23. package/dist/server/assets/{session-detail.server-azkRfON2.js → session-detail.server-DLXl-Pn-.js} +1 -1
  24. package/dist/server/assets/session-scanner-CLfls9u-.js +93 -0
  25. package/dist/server/assets/sessions.queries-B5ZBiVJy.js +42 -0
  26. package/dist/server/assets/{sessions.server-B8zbmvSM.js → sessions.server-CUhasKW2.js} +5 -89
  27. package/dist/server/assets/{settings-ko61yfVs.js → settings-C0_KyVQQ.js} +66 -20
  28. package/dist/server/assets/stats-BtgVene-.js +886 -0
  29. package/dist/server/assets/{stats.server-BZWxV-mC.js → stats.server-qTOvID9-.js} +62 -3
  30. package/dist/server/assets/useSessionCost-CYs5UOX-.js +209 -0
  31. package/dist/server/server.js +13 -10
  32. package/package.json +11 -1
  33. package/dist/client/assets/_dashboard-CYwTENkn.js +0 -1
  34. package/dist/client/assets/_sessionId-Bwfhm_El.js +0 -12
  35. package/dist/client/assets/app-DhZyFob1.css +0 -1
  36. package/dist/client/assets/format-Bf-cSf6L.js +0 -1
  37. package/dist/client/assets/index-DXhX1hdS.js +0 -1
  38. package/dist/client/assets/settings-BSPc79zZ.js +0 -1
  39. package/dist/client/assets/stats-CDIvpOt9.js +0 -4
  40. package/dist/client/assets/useSessionCost-9NP6uhla.js +0 -61
  41. package/dist/server/assets/_dashboard--ukhquwO.js +0 -97
  42. package/dist/server/assets/_tanstack-start-manifest_v-gtQY7f-T.js +0 -4
  43. package/dist/server/assets/stats-DItsFPp5.js +0 -266
  44. package/dist/server/assets/useSessionCost-EB0VxklP.js +0 -76
@@ -1,97 +0,0 @@
1
- import { jsxs, jsx } from "react/jsx-runtime";
2
- import { useMatches, Link, Outlet } from "@tanstack/react-router";
3
- import { u as usePrivacy } from "./router-xTSe9UH_.js";
4
- import "@tanstack/react-query";
5
- import "react";
6
- import "zod";
7
- function PrivacyToggle() {
8
- const { privacyMode, togglePrivacyMode } = usePrivacy();
9
- return /* @__PURE__ */ jsxs(
10
- "button",
11
- {
12
- type: "button",
13
- onClick: togglePrivacyMode,
14
- title: privacyMode ? "Privacy mode on" : "Privacy mode off",
15
- className: `flex items-center gap-2 rounded-lg px-3 py-1.5 text-xs transition-colors ${privacyMode ? "bg-blue-600 text-white" : "bg-gray-800 text-gray-400 hover:text-gray-200"}`,
16
- children: [
17
- /* @__PURE__ */ jsxs(
18
- "svg",
19
- {
20
- xmlns: "http://www.w3.org/2000/svg",
21
- viewBox: "0 0 20 20",
22
- fill: "currentColor",
23
- className: "h-3.5 w-3.5",
24
- children: [
25
- privacyMode ? (
26
- // Eye-off icon
27
- /* @__PURE__ */ jsx(
28
- "path",
29
- {
30
- fillRule: "evenodd",
31
- d: "M3.28 2.22a.75.75 0 00-1.06 1.06l14.5 14.5a.75.75 0 101.06-1.06l-1.745-1.745a10.029 10.029 0 003.3-4.38 1.651 1.651 0 000-1.185A10.004 10.004 0 009.999 3a9.956 9.956 0 00-4.744 1.194L3.28 2.22zM7.752 6.69l1.092 1.092a2.5 2.5 0 013.374 3.373l1.092 1.092a4 4 0 00-5.558-5.558z",
32
- clipRule: "evenodd"
33
- }
34
- )
35
- ) : (
36
- // Eye icon
37
- /* @__PURE__ */ jsx("path", { d: "M10 12.5a2.5 2.5 0 100-5 2.5 2.5 0 000 5z" })
38
- ),
39
- privacyMode ? /* @__PURE__ */ jsx("path", { d: "M10.748 13.93l2.523 2.523a9.987 9.987 0 01-3.27.547c-4.258 0-7.894-2.66-9.337-6.41a1.651 1.651 0 010-1.186A10.007 10.007 0 012.839 6.02L6.07 9.252a4 4 0 004.678 4.678z" }) : /* @__PURE__ */ jsx(
40
- "path",
41
- {
42
- fillRule: "evenodd",
43
- d: "M.458 10a9.996 9.996 0 019.542-6c4.258 0 7.894 2.66 9.337 6.41a1.651 1.651 0 010 1.186A10.004 10.004 0 0110 17.5c-4.258 0-7.894-2.66-9.337-6.41a1.651 1.651 0 010-1.186L.458 10zM14 10a4 4 0 11-8 0 4 4 0 018 0z",
44
- clipRule: "evenodd"
45
- }
46
- )
47
- ]
48
- }
49
- ),
50
- "Privacy"
51
- ]
52
- }
53
- );
54
- }
55
- const NAV_ITEMS = [
56
- { to: "/sessions", label: "Sessions", icon: ">" },
57
- { to: "/stats", label: "Stats", icon: "#" },
58
- { to: "/settings", label: "Settings", icon: "*" }
59
- ];
60
- function AppShell({ children }) {
61
- const matches = useMatches();
62
- const currentPath = matches[matches.length - 1]?.pathname ?? "";
63
- return /* @__PURE__ */ jsxs("div", { className: "flex min-h-screen", children: [
64
- /* @__PURE__ */ jsxs("aside", { className: "flex w-56 shrink-0 flex-col border-r border-gray-800 bg-gray-950", children: [
65
- /* @__PURE__ */ jsx("div", { className: "flex h-14 items-center border-b border-gray-800 px-4", children: /* @__PURE__ */ jsxs(Link, { to: "/sessions", className: "text-sm font-bold text-white", children: [
66
- /* @__PURE__ */ jsx("span", { className: "text-blue-400", children: "Claude" }),
67
- " Dashboard"
68
- ] }) }),
69
- /* @__PURE__ */ jsx("nav", { className: "flex-1 p-3", children: NAV_ITEMS.map((item) => {
70
- const isActive = currentPath.startsWith(item.to);
71
- return /* @__PURE__ */ jsxs(
72
- Link,
73
- {
74
- to: item.to,
75
- className: `flex items-center gap-2.5 rounded-lg px-3 py-2 text-sm transition-colors ${isActive ? "bg-gray-800 text-white" : "text-gray-400 hover:bg-gray-800/50 hover:text-gray-200"}`,
76
- children: [
77
- /* @__PURE__ */ jsx("span", { className: "font-mono text-xs text-gray-500", children: item.icon }),
78
- item.label
79
- ]
80
- },
81
- item.to
82
- );
83
- }) }),
84
- /* @__PURE__ */ jsxs("div", { className: "border-t border-gray-800 p-3", children: [
85
- /* @__PURE__ */ jsx(PrivacyToggle, {}),
86
- /* @__PURE__ */ jsx("p", { className: "mt-2 text-xs text-gray-600", children: "Read-only observer" })
87
- ] })
88
- ] }),
89
- /* @__PURE__ */ jsx("main", { className: "flex-1 overflow-auto", children: /* @__PURE__ */ jsx("div", { className: "mx-auto max-w-5xl px-6 py-6", children }) })
90
- ] });
91
- }
92
- function DashboardLayout() {
93
- return /* @__PURE__ */ jsx(AppShell, { children: /* @__PURE__ */ jsx(Outlet, {}) });
94
- }
95
- export {
96
- DashboardLayout as component
97
- };
@@ -1,4 +0,0 @@
1
- const tsrStartManifest = () => ({ "routes": { "__root__": { "filePath": "/Users/dmytro.lupiak/Documents/GitHub/claude-session-dashboard/apps/web/src/routes/__root.tsx", "children": ["/", "/_dashboard"], "preloads": ["/assets/main-CzD8HjLq.js"], "assets": [] }, "/": { "filePath": "/Users/dmytro.lupiak/Documents/GitHub/claude-session-dashboard/apps/web/src/routes/index.tsx" }, "/_dashboard": { "filePath": "/Users/dmytro.lupiak/Documents/GitHub/claude-session-dashboard/apps/web/src/routes/_dashboard.tsx", "children": ["/_dashboard/settings", "/_dashboard/stats", "/_dashboard/sessions/$sessionId", "/_dashboard/sessions/"], "assets": [], "preloads": ["/assets/_dashboard-CYwTENkn.js"] }, "/_dashboard/settings": { "filePath": "/Users/dmytro.lupiak/Documents/GitHub/claude-session-dashboard/apps/web/src/routes/_dashboard/settings.tsx", "assets": [], "preloads": ["/assets/settings-BSPc79zZ.js", "/assets/createServerFn-Le0d8Pjz.js", "/assets/settings.types-B4841OLF.js"] }, "/_dashboard/stats": { "filePath": "/Users/dmytro.lupiak/Documents/GitHub/claude-session-dashboard/apps/web/src/routes/_dashboard/stats.tsx", "assets": [], "preloads": ["/assets/stats-CDIvpOt9.js", "/assets/createServerFn-Le0d8Pjz.js", "/assets/format-Bf-cSf6L.js", "/assets/useSessionCost-9NP6uhla.js", "/assets/settings.types-B4841OLF.js"] }, "/_dashboard/sessions/$sessionId": { "filePath": "/Users/dmytro.lupiak/Documents/GitHub/claude-session-dashboard/apps/web/src/routes/_dashboard/sessions/$sessionId.tsx", "assets": [], "preloads": ["/assets/_sessionId-Bwfhm_El.js", "/assets/createServerFn-Le0d8Pjz.js", "/assets/format-Bf-cSf6L.js", "/assets/useSessionCost-9NP6uhla.js", "/assets/settings.types-B4841OLF.js"] }, "/_dashboard/sessions/": { "filePath": "/Users/dmytro.lupiak/Documents/GitHub/claude-session-dashboard/apps/web/src/routes/_dashboard/sessions/index.tsx", "assets": [], "preloads": ["/assets/index-DXhX1hdS.js", "/assets/createServerFn-Le0d8Pjz.js", "/assets/format-Bf-cSf6L.js"] } }, "clientEntry": "/assets/main-CzD8HjLq.js" });
2
- export {
3
- tsrStartManifest
4
- };
@@ -1,266 +0,0 @@
1
- import { jsxs, jsx } from "react/jsx-runtime";
2
- import { useMemo } from "react";
3
- import { queryOptions, useQuery } from "@tanstack/react-query";
4
- import { c as createSsrRpc } from "./createSsrRpc-CVg2UDl0.js";
5
- import { c as createServerFn } from "../server.js";
6
- import { ResponsiveContainer, BarChart, CartesianGrid, XAxis, YAxis, Tooltip, Bar, PieChart, Pie, Cell, Legend } from "recharts";
7
- import { format } from "date-fns";
8
- import { f as formatTokenCount, a as formatUSD, b as formatDuration } from "./format-CGmJnuhZ.js";
9
- import { u as useSessionCost } from "./useSessionCost-EB0VxklP.js";
10
- import "@tanstack/history";
11
- import "@tanstack/router-core/ssr/client";
12
- import "@tanstack/router-core";
13
- import "node:async_hooks";
14
- import "@tanstack/router-core/ssr/server";
15
- import "h3-v2";
16
- import "tiny-invariant";
17
- import "seroval";
18
- import "@tanstack/react-router/ssr/server";
19
- import "@tanstack/react-router";
20
- import "./settings.queries-DSQd324O.js";
21
- import "./settings.types-DntadCHo.js";
22
- import "zod";
23
- const getStats = createServerFn({
24
- method: "GET"
25
- }).handler(createSsrRpc("4b9a58c176f487b49800a372100037cdf33cf048f3592a449f115c7e3f5ea799"));
26
- const statsQuery = queryOptions({
27
- queryKey: ["stats"],
28
- queryFn: () => getStats(),
29
- refetchInterval: 6e4
30
- });
31
- function ActivityChart({ data }) {
32
- const chartData = data.map((d) => ({
33
- ...d,
34
- dateLabel: format(new Date(d.date), "MMM d")
35
- }));
36
- return /* @__PURE__ */ jsxs("div", { className: "rounded-xl border border-gray-800 bg-gray-900/50 p-4", children: [
37
- /* @__PURE__ */ jsx("h3", { className: "text-sm font-semibold text-gray-300", children: "Daily Activity" }),
38
- /* @__PURE__ */ jsx("p", { className: "mt-1 text-xs text-gray-500", children: "Messages, sessions, and tool calls per day" }),
39
- /* @__PURE__ */ jsx("div", { className: "mt-4 h-64", children: /* @__PURE__ */ jsx(ResponsiveContainer, { width: "100%", height: "100%", children: /* @__PURE__ */ jsxs(BarChart, { data: chartData, children: [
40
- /* @__PURE__ */ jsx(CartesianGrid, { strokeDasharray: "3 3", stroke: "#1f2937" }),
41
- /* @__PURE__ */ jsx(
42
- XAxis,
43
- {
44
- dataKey: "dateLabel",
45
- tick: { fill: "#6b7280", fontSize: 10 },
46
- tickLine: false
47
- }
48
- ),
49
- /* @__PURE__ */ jsx(
50
- YAxis,
51
- {
52
- tick: { fill: "#6b7280", fontSize: 10 },
53
- tickLine: false,
54
- axisLine: false
55
- }
56
- ),
57
- /* @__PURE__ */ jsx(
58
- Tooltip,
59
- {
60
- contentStyle: {
61
- backgroundColor: "#111827",
62
- border: "1px solid #374151",
63
- borderRadius: "8px",
64
- fontSize: "12px"
65
- }
66
- }
67
- ),
68
- /* @__PURE__ */ jsx(
69
- Bar,
70
- {
71
- dataKey: "messageCount",
72
- name: "Messages",
73
- fill: "#3b82f6",
74
- radius: [2, 2, 0, 0]
75
- }
76
- ),
77
- /* @__PURE__ */ jsx(
78
- Bar,
79
- {
80
- dataKey: "toolCallCount",
81
- name: "Tool Calls",
82
- fill: "#8b5cf6",
83
- radius: [2, 2, 0, 0]
84
- }
85
- ),
86
- /* @__PURE__ */ jsx(
87
- Bar,
88
- {
89
- dataKey: "sessionCount",
90
- name: "Sessions",
91
- fill: "#10b981",
92
- radius: [2, 2, 0, 0]
93
- }
94
- )
95
- ] }) }) })
96
- ] });
97
- }
98
- const COLORS = ["#3b82f6", "#8b5cf6", "#10b981", "#f59e0b", "#ef4444", "#6366f1"];
99
- function ModelUsageChart({ data }) {
100
- const chartData = Object.entries(data).map(([model, usage]) => ({
101
- name: model.replace(/^claude-/, "").split("-202")[0],
102
- fullName: model,
103
- totalTokens: usage.inputTokens + usage.outputTokens,
104
- outputTokens: usage.outputTokens
105
- }));
106
- chartData.sort((a, b) => b.totalTokens - a.totalTokens);
107
- return /* @__PURE__ */ jsxs("div", { className: "rounded-xl border border-gray-800 bg-gray-900/50 p-4", children: [
108
- /* @__PURE__ */ jsx("h3", { className: "text-sm font-semibold text-gray-300", children: "Model Usage" }),
109
- /* @__PURE__ */ jsx("p", { className: "mt-1 text-xs text-gray-500", children: "Token usage by model" }),
110
- /* @__PURE__ */ jsx("div", { className: "mt-4 h-64", children: /* @__PURE__ */ jsx(ResponsiveContainer, { width: "100%", height: "100%", children: /* @__PURE__ */ jsxs(PieChart, { children: [
111
- /* @__PURE__ */ jsx(
112
- Pie,
113
- {
114
- data: chartData,
115
- cx: "50%",
116
- cy: "50%",
117
- innerRadius: 50,
118
- outerRadius: 80,
119
- dataKey: "totalTokens",
120
- nameKey: "name",
121
- strokeWidth: 0,
122
- children: chartData.map((_, i) => /* @__PURE__ */ jsx(Cell, { fill: COLORS[i % COLORS.length], opacity: 0.8 }, i))
123
- }
124
- ),
125
- /* @__PURE__ */ jsx(
126
- Tooltip,
127
- {
128
- formatter: (value) => formatTokenCount(value),
129
- contentStyle: {
130
- backgroundColor: "#111827",
131
- border: "1px solid #374151",
132
- borderRadius: "8px",
133
- fontSize: "12px"
134
- }
135
- }
136
- ),
137
- /* @__PURE__ */ jsx(
138
- Legend,
139
- {
140
- wrapperStyle: { fontSize: "11px" }
141
- }
142
- )
143
- ] }) }) })
144
- ] });
145
- }
146
- function HourlyDistribution({
147
- hourCounts
148
- }) {
149
- const hours = Array.from({ length: 24 }, (_, i) => ({
150
- hour: i,
151
- label: `${i.toString().padStart(2, "0")}:00`,
152
- count: hourCounts[String(i)] ?? 0
153
- }));
154
- const maxCount = Math.max(...hours.map((h) => h.count), 1);
155
- return /* @__PURE__ */ jsxs("div", { className: "rounded-xl border border-gray-800 bg-gray-900/50 p-4", children: [
156
- /* @__PURE__ */ jsx("h3", { className: "text-sm font-semibold text-gray-300", children: "Hourly Distribution" }),
157
- /* @__PURE__ */ jsx("p", { className: "mt-1 text-xs text-gray-500", children: "Session starts by hour of day" }),
158
- /* @__PURE__ */ jsx("div", { className: "mt-4 flex items-end gap-0.5", style: { height: "120px" }, children: hours.map((h) => {
159
- const height = h.count > 0 ? Math.max(h.count / maxCount * 100, 4) : 0;
160
- const intensity = h.count / maxCount;
161
- return /* @__PURE__ */ jsxs(
162
- "div",
163
- {
164
- className: "group relative flex-1",
165
- style: { height: "100%" },
166
- children: [
167
- /* @__PURE__ */ jsx(
168
- "div",
169
- {
170
- className: "absolute bottom-0 w-full rounded-t transition-colors",
171
- style: {
172
- height: `${height}%`,
173
- backgroundColor: `rgba(59, 130, 246, ${0.2 + intensity * 0.6})`
174
- }
175
- }
176
- ),
177
- /* @__PURE__ */ jsxs("div", { className: "absolute -top-8 left-1/2 hidden -translate-x-1/2 whitespace-nowrap rounded bg-gray-800 px-1.5 py-0.5 text-[10px] text-gray-300 group-hover:block", children: [
178
- h.label,
179
- ": ",
180
- h.count
181
- ] })
182
- ]
183
- },
184
- h.hour
185
- );
186
- }) }),
187
- /* @__PURE__ */ jsxs("div", { className: "mt-1 flex justify-between text-[10px] text-gray-600", children: [
188
- /* @__PURE__ */ jsx("span", { children: "00:00" }),
189
- /* @__PURE__ */ jsx("span", { children: "06:00" }),
190
- /* @__PURE__ */ jsx("span", { children: "12:00" }),
191
- /* @__PURE__ */ jsx("span", { children: "18:00" }),
192
- /* @__PURE__ */ jsx("span", { children: "23:00" })
193
- ] })
194
- ] });
195
- }
196
- const EMPTY_TOKENS_BY_MODEL = {};
197
- function StatsPage() {
198
- const {
199
- data: stats,
200
- isLoading
201
- } = useQuery(statsQuery);
202
- const tokensByModel = useMemo(() => {
203
- if (!stats) return EMPTY_TOKENS_BY_MODEL;
204
- const result = {};
205
- for (const [model, usage] of Object.entries(stats.modelUsage)) {
206
- result[model] = {
207
- inputTokens: usage.inputTokens,
208
- outputTokens: usage.outputTokens,
209
- cacheReadInputTokens: usage.cacheReadInputTokens,
210
- cacheCreationInputTokens: usage.cacheCreationInputTokens
211
- };
212
- }
213
- return result;
214
- }, [stats]);
215
- const {
216
- cost
217
- } = useSessionCost(tokensByModel);
218
- if (isLoading) {
219
- return /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
220
- /* @__PURE__ */ jsx("div", { className: "h-8 w-32 animate-pulse rounded bg-gray-800" }),
221
- Array.from({
222
- length: 3
223
- }).map((_, i) => /* @__PURE__ */ jsx("div", { className: "h-64 animate-pulse rounded-xl bg-gray-900/50" }, i))
224
- ] });
225
- }
226
- if (!stats) {
227
- return /* @__PURE__ */ jsx("div", { className: "py-12 text-center text-sm text-gray-500", children: "No stats data found. Check ~/.claude/stats-cache.json" });
228
- }
229
- const totalTokens = Object.values(stats.modelUsage).reduce((sum, m) => sum + m.inputTokens + m.outputTokens, 0);
230
- return /* @__PURE__ */ jsxs("div", { children: [
231
- /* @__PURE__ */ jsx("h1", { className: "text-2xl font-bold text-white", children: "Stats" }),
232
- /* @__PURE__ */ jsxs("p", { className: "mt-1 text-sm text-gray-400", children: [
233
- "Aggregate usage from ",
234
- stats.firstSessionDate?.split("T")[0],
235
- " to",
236
- " ",
237
- stats.lastComputedDate
238
- ] }),
239
- /* @__PURE__ */ jsxs("div", { className: "mt-6 grid grid-cols-2 gap-3 md:grid-cols-5", children: [
240
- /* @__PURE__ */ jsx(StatCard, { label: "Total Sessions", value: String(stats.totalSessions) }),
241
- /* @__PURE__ */ jsx(StatCard, { label: "Total Messages", value: stats.totalMessages.toLocaleString() }),
242
- /* @__PURE__ */ jsx(StatCard, { label: "Total Tokens", value: formatTokenCount(totalTokens), sub: cost ? `~${formatUSD(cost.totalUSD)}` : void 0 }),
243
- /* @__PURE__ */ jsx(StatCard, { label: "Total Estimated Cost", value: cost ? `~${formatUSD(cost.totalUSD)}` : "N/A" }),
244
- /* @__PURE__ */ jsx(StatCard, { label: "Longest Session", value: formatDuration(stats.longestSession.duration), sub: `${stats.longestSession.messageCount} messages` })
245
- ] }),
246
- /* @__PURE__ */ jsx("div", { className: "mt-6", children: /* @__PURE__ */ jsx(ActivityChart, { data: stats.dailyActivity }) }),
247
- /* @__PURE__ */ jsxs("div", { className: "mt-4 grid grid-cols-1 gap-4 md:grid-cols-2", children: [
248
- /* @__PURE__ */ jsx(ModelUsageChart, { data: stats.modelUsage }),
249
- /* @__PURE__ */ jsx(HourlyDistribution, { hourCounts: stats.hourCounts })
250
- ] })
251
- ] });
252
- }
253
- function StatCard({
254
- label,
255
- value,
256
- sub
257
- }) {
258
- return /* @__PURE__ */ jsxs("div", { className: "rounded-xl border border-gray-800 bg-gray-900/50 p-4", children: [
259
- /* @__PURE__ */ jsx("p", { className: "text-xs text-gray-400", children: label }),
260
- /* @__PURE__ */ jsx("p", { className: "mt-1 text-xl font-bold text-white", children: value }),
261
- sub && /* @__PURE__ */ jsx("p", { className: "mt-0.5 text-xs text-gray-500", children: sub })
262
- ] });
263
- }
264
- export {
265
- StatsPage as component
266
- };
@@ -1,76 +0,0 @@
1
- import { useMemo } from "react";
2
- import { useQuery } from "@tanstack/react-query";
3
- import { s as settingsQuery } from "./settings.queries-DSQd324O.js";
4
- import { b as DEFAULT_PRICING, n as normalizeModelId } from "./settings.types-DntadCHo.js";
5
- function getMergedPricing(settings) {
6
- const table = {};
7
- for (const model of DEFAULT_PRICING) {
8
- const override = settings.pricingOverrides[model.modelId];
9
- table[model.modelId] = override ? { ...model, ...override } : { ...model };
10
- }
11
- return table;
12
- }
13
- const FALLBACK_MODEL_ID = "claude-sonnet-4";
14
- function calculateSessionCost(tokensByModel, pricingTable) {
15
- const byModel = {};
16
- const byCategory = {
17
- input: 0,
18
- output: 0,
19
- cacheRead: 0,
20
- cacheWrite: 0
21
- };
22
- for (const [rawModelId, tokens] of Object.entries(tokensByModel)) {
23
- const normalized = normalizeModelId(rawModelId);
24
- const pricing = pricingTable[normalized] ?? pricingTable[FALLBACK_MODEL_ID];
25
- if (!pricing) continue;
26
- const inputCost = tokens.inputTokens / 1e6 * pricing.inputPerMTok;
27
- const outputCost = tokens.outputTokens / 1e6 * pricing.outputPerMTok;
28
- const cacheReadCost = tokens.cacheReadInputTokens / 1e6 * pricing.cacheReadPerMTok;
29
- const cacheWriteCost = tokens.cacheCreationInputTokens / 1e6 * pricing.cacheWritePerMTok;
30
- const totalCost = inputCost + outputCost + cacheReadCost + cacheWriteCost;
31
- const existing = byModel[normalized];
32
- if (existing) {
33
- existing.inputCost += inputCost;
34
- existing.outputCost += outputCost;
35
- existing.cacheReadCost += cacheReadCost;
36
- existing.cacheWriteCost += cacheWriteCost;
37
- existing.totalCost += totalCost;
38
- existing.tokens.inputTokens += tokens.inputTokens;
39
- existing.tokens.outputTokens += tokens.outputTokens;
40
- existing.tokens.cacheReadInputTokens += tokens.cacheReadInputTokens;
41
- existing.tokens.cacheCreationInputTokens += tokens.cacheCreationInputTokens;
42
- } else {
43
- byModel[normalized] = {
44
- modelId: normalized,
45
- displayName: pricing.displayName,
46
- inputCost,
47
- outputCost,
48
- cacheReadCost,
49
- cacheWriteCost,
50
- totalCost,
51
- tokens: { ...tokens }
52
- };
53
- }
54
- byCategory.input += inputCost;
55
- byCategory.output += outputCost;
56
- byCategory.cacheRead += cacheReadCost;
57
- byCategory.cacheWrite += cacheWriteCost;
58
- }
59
- const totalUSD = byCategory.input + byCategory.output + byCategory.cacheRead + byCategory.cacheWrite;
60
- return { totalUSD, byModel, byCategory };
61
- }
62
- function useSessionCost(tokensByModel) {
63
- const { data: settings, isLoading } = useQuery(settingsQuery);
64
- const cost = useMemo(() => {
65
- if (!settings) return null;
66
- if (Object.keys(tokensByModel).length === 0) return null;
67
- const pricingTable = getMergedPricing(settings);
68
- return calculateSessionCost(tokensByModel, pricingTable);
69
- }, [settings, tokensByModel]);
70
- return { cost, isLoading };
71
- }
72
- export {
73
- calculateSessionCost as c,
74
- getMergedPricing as g,
75
- useSessionCost as u
76
- };