diff --git a/app/overview-client.tsx b/app/overview-client.tsx
index b12f449..c1aab73 100644
--- a/app/overview-client.tsx
+++ b/app/overview-client.tsx
@@ -6,7 +6,7 @@ import Link from "next/link";
import { BarChart3, PieChart } from "lucide-react";
import { UsageOverTimeChart } from "@/components/overview/usage-over-time-chart";
import { ModelBreakdownDonut } from "@/components/overview/model-breakdown-donut";
-import { ProjectActivityDonut } from "@/components/overview/project-activity-donut";
+import { ProjectActivityChart } from "@/components/overview/project-activity-chart";
import { PeakHoursChart } from "@/components/overview/peak-hours-chart";
import { OverviewConversationTable } from "@/components/overview/conversation-table";
import { formatTokens, formatBytes } from "@/lib/decode";
@@ -283,7 +283,7 @@ export function OverviewClient() {
icon={}
title="Project activity distribution"
>
-
+
diff --git a/components/overview/model-breakdown-donut.tsx b/components/overview/model-breakdown-donut.tsx
index d5f04ad..671928c 100644
--- a/components/overview/model-breakdown-donut.tsx
+++ b/components/overview/model-breakdown-donut.tsx
@@ -47,18 +47,17 @@ function CustomTooltip({ active, payload }: any) {
}
export function ModelBreakdownDonut({ modelUsage }: Props) {
+ // I/O tokens only — cache tokens are a different dimension and inflate Opus disproportionately
const data = Object.entries(modelUsage)
.map(([model, usage]) => ({
name: shortModelName(model),
- value:
- (usage.inputTokens ?? 0) +
- (usage.outputTokens ?? 0) +
- (usage.cacheReadInputTokens ?? 0) +
- (usage.cacheCreationInputTokens ?? 0),
+ value: (usage.inputTokens ?? 0) + (usage.outputTokens ?? 0),
}))
.filter((d) => d.value > 0)
.sort((a, b) => b.value - a.value);
+ const total = data.reduce((s, d) => s + d.value, 0);
+
if (data.length === 0) {
return (
@@ -90,11 +89,17 @@ export function ModelBreakdownDonut({ modelUsage }: Props) {
iconType="circle"
iconSize={8}
wrapperStyle={{ fontSize: 12 }}
- formatter={(value) => (
-
- {value}
-
- )}
+ formatter={(value: string, _entry: unknown, index: number) => {
+ const pct =
+ total > 0 ? Math.round((data[index].value / total) * 100) : 0;
+ return (
+
+ {value} ({pct}%)
+
+ );
+ }}
/>
diff --git a/components/overview/project-activity-chart.tsx b/components/overview/project-activity-chart.tsx
new file mode 100644
index 0000000..dc9408f
--- /dev/null
+++ b/components/overview/project-activity-chart.tsx
@@ -0,0 +1,101 @@
+"use client";
+
+import {
+ BarChart,
+ Bar,
+ XAxis,
+ YAxis,
+ Tooltip,
+ ResponsiveContainer,
+ CartesianGrid,
+} from "recharts";
+import type { ProjectSummary } from "@/types/claude";
+import { formatTokens } from "@/lib/decode";
+
+interface Props {
+ projects: ProjectSummary[];
+}
+
+// eslint-disable-next-line @typescript-eslint/no-explicit-any
+function CustomTooltip({ active, payload, label }: any) {
+ if (!active || !payload?.length) return null;
+ return (
+
+
{label}
+
+ {formatTokens(payload[0].value)} tokens
+
+ {payload[0].payload.sessions != null && (
+
+ {payload[0].payload.sessions} sessions
+
+ )}
+
+ );
+}
+
+export function ProjectActivityChart({ projects }: Props) {
+ const data = projects
+ .slice(0, 8)
+ .map((p) => ({
+ name:
+ p.display_name.length > 20
+ ? p.display_name.slice(0, 18) + "..."
+ : p.display_name,
+ fullName: p.display_name,
+ value: (p.input_tokens ?? 0) + (p.output_tokens ?? 0),
+ sessions: p.session_count ?? 0,
+ }))
+ .filter((d) => d.value > 0);
+
+ if (data.length === 0) {
+ return (
+
+ no project data
+
+ );
+ }
+
+ return (
+
+
+
+
+ formatTokens(v)}
+ />
+
+ } />
+
+
+
+
+ );
+}