+
Reads: {formatCompact(metrics.totalCacheReads)}
Writes: {formatCompact(metrics.totalCacheWrites)}
diff --git a/ui/src/components/analytics/model-breakdown-chart.tsx b/ui/src/components/analytics/model-breakdown-chart.tsx
index 9ef39a0e..b08e43f1 100644
--- a/ui/src/components/analytics/model-breakdown-chart.tsx
+++ b/ui/src/components/analytics/model-breakdown-chart.tsx
@@ -2,7 +2,7 @@
* Model Breakdown Chart Component
*
* Displays usage distribution by model using pie chart.
- * Shows tokens, cost, and percentage breakdown.
+ * Shows tokens, cost, and percentage breakdown. Respects privacy mode.
*/
import { useMemo } from 'react';
@@ -10,6 +10,7 @@ import { PieChart, Pie, Cell, ResponsiveContainer, Tooltip } from 'recharts';
import { Skeleton } from '@/components/ui/skeleton';
import type { ModelUsage } from '@/hooks/use-usage';
import { cn, getModelColor } from '@/lib/utils';
+import { usePrivacy, PRIVACY_BLUR_CLASS } from '@/contexts/privacy-context';
interface ModelBreakdownChartProps {
data: ModelUsage[];
@@ -18,6 +19,8 @@ interface ModelBreakdownChartProps {
}
export function ModelBreakdownChart({ data, isLoading, className }: ModelBreakdownChartProps) {
+ const { privacyMode } = usePrivacy();
+
const chartData = useMemo(() => {
if (!data || data.length === 0) return [];
@@ -54,10 +57,12 @@ export function ModelBreakdownChart({ data, isLoading, className }: ModelBreakdo
return (
{item.name}
-
+
{formatNumber(item.value)} ({item.percentage.toFixed(1)}%)
-
${item.cost.toFixed(4)}
+
+ ${item.cost.toFixed(4)}
+
);
};
diff --git a/ui/src/components/analytics/model-details-content.tsx b/ui/src/components/analytics/model-details-content.tsx
index 2b813283..4aca6511 100644
--- a/ui/src/components/analytics/model-details-content.tsx
+++ b/ui/src/components/analytics/model-details-content.tsx
@@ -1,12 +1,15 @@
import { Badge } from '@/components/ui/badge';
import { ArrowDownRight, ArrowUpRight, Database, Gauge, Sparkles } from 'lucide-react';
import type { ModelUsage } from '@/hooks/use-usage';
+import { usePrivacy, PRIVACY_BLUR_CLASS } from '@/contexts/privacy-context';
+import { cn } from '@/lib/utils';
interface ModelDetailsContentProps {
model: ModelUsage;
}
export function ModelDetailsContent({ model }: ModelDetailsContentProps) {
+ const { privacyMode } = usePrivacy();
const ioRatioStatus = getIoRatioStatus(model.ioRatio);
return (
@@ -32,11 +35,15 @@ export function ModelDetailsContent({ model }: ModelDetailsContentProps) {
{/* Stats Grid */}
-
${model.cost.toFixed(2)}
+
+ ${model.cost.toFixed(2)}
+
Total Cost
-
{formatCompactNumber(model.tokens)}
+
+ {formatCompactNumber(model.tokens)}
+
Total Tokens
@@ -46,7 +53,7 @@ export function ModelDetailsContent({ model }: ModelDetailsContentProps) {
Token Breakdown
-
+
{
if (!data?.sessions || data.sessions.length === 0) return null;
@@ -104,7 +107,9 @@ export function SessionStatsCard({ data, isLoading, className }: SessionStatsCar
- ${stats.avgCost.toFixed(2)}
+
+ ${stats.avgCost.toFixed(2)}
+
Avg Cost/Session
@@ -132,7 +137,7 @@ export function SessionStatsCard({ data, isLoading, className }: SessionStatsCar
{formatDistanceToNow(new Date(session.lastActivity), { addSuffix: true })}
-
+
${session.cost.toFixed(2)}
{formatCompact(session.inputTokens + session.outputTokens)} toks
diff --git a/ui/src/components/analytics/token-breakdown-chart.tsx b/ui/src/components/analytics/token-breakdown-chart.tsx
index 01f90913..24817750 100644
--- a/ui/src/components/analytics/token-breakdown-chart.tsx
+++ b/ui/src/components/analytics/token-breakdown-chart.tsx
@@ -2,7 +2,7 @@
* Token Breakdown Chart Component
*
* Displays token usage breakdown by type (input, output, cache).
- * Shows stacked bar chart with cost breakdown.
+ * Shows stacked bar chart with cost breakdown. Respects privacy mode.
*/
import { useMemo } from 'react';
@@ -19,6 +19,7 @@ import {
import { Skeleton } from '@/components/ui/skeleton';
import type { TokenBreakdown } from '@/hooks/use-usage';
import { cn } from '@/lib/utils';
+import { usePrivacy, PRIVACY_BLUR_CLASS } from '@/contexts/privacy-context';
interface TokenBreakdownChartProps {
data?: TokenBreakdown;
@@ -34,6 +35,8 @@ const COLORS = {
};
export function TokenBreakdownChart({ data, isLoading, className }: TokenBreakdownChartProps) {
+ const { privacyMode } = usePrivacy();
+
const chartData = useMemo(() => {
if (!data) return [];
@@ -151,7 +154,12 @@ export function TokenBreakdownChart({ data, isLoading, className }: TokenBreakdo
{/* Cost breakdown summary */}
-
+
{chartData.map((item) => (
diff --git a/ui/src/components/analytics/usage-summary-cards.tsx b/ui/src/components/analytics/usage-summary-cards.tsx
index cde345f2..301df4ab 100644
--- a/ui/src/components/analytics/usage-summary-cards.tsx
+++ b/ui/src/components/analytics/usage-summary-cards.tsx
@@ -3,6 +3,7 @@
*
* Displays key metrics in a card grid layout.
* Shows total tokens, cost, cache tokens, and average cost per day.
+ * Respects privacy mode to blur sensitive financial data.
*/
import { Card, CardContent } from '@/components/ui/card';
@@ -10,6 +11,7 @@ import { Skeleton } from '@/components/ui/skeleton';
import { DollarSign, Database, FileText, ArrowDownRight, ArrowUpRight } from 'lucide-react';
import { cn } from '@/lib/utils';
import type { UsageSummary } from '@/hooks/use-usage';
+import { usePrivacy, PRIVACY_BLUR_CLASS } from '@/contexts/privacy-context';
interface UsageSummaryCardsProps {
data?: UsageSummary;
@@ -17,6 +19,8 @@ interface UsageSummaryCardsProps {
}
export function UsageSummaryCards({ data, isLoading }: UsageSummaryCardsProps) {
+ const { privacyMode } = usePrivacy();
+
if (isLoading) {
return (
@@ -100,9 +104,20 @@ export function UsageSummaryCards({ data, isLoading }: UsageSummaryCardsProps) {
{card.title}
-
{card.format(card.value)}
+
+ {card.format(card.value)}
+
{card.subtitle && (
-
{card.subtitle}
+
+ {card.subtitle}
+
)}
diff --git a/ui/src/components/analytics/usage-trend-chart.tsx b/ui/src/components/analytics/usage-trend-chart.tsx
index d4bc7eeb..c3a47653 100644
--- a/ui/src/components/analytics/usage-trend-chart.tsx
+++ b/ui/src/components/analytics/usage-trend-chart.tsx
@@ -3,6 +3,7 @@
*
* Displays usage trends over time with tokens and cost.
* Supports daily, hourly, and monthly granularity with interactive tooltips.
+ * Respects privacy mode to blur sensitive data.
*/
import { useMemo } from 'react';
@@ -19,6 +20,7 @@ import { format } from 'date-fns';
import { Skeleton } from '@/components/ui/skeleton';
import { cn } from '@/lib/utils';
import type { DailyUsage, HourlyUsage } from '@/hooks/use-usage';
+import { usePrivacy, PRIVACY_BLUR_CLASS } from '@/contexts/privacy-context';
type ChartData = DailyUsage | HourlyUsage;
@@ -35,6 +37,8 @@ export function UsageTrendChart({
granularity = 'daily',
className,
}: UsageTrendChartProps) {
+ const { privacyMode } = usePrivacy();
+
const chartData = useMemo(() => {
if (!data || data.length === 0) return [];
@@ -66,6 +70,35 @@ export function UsageTrendChart({
);
}
+ // Custom tick component for privacy-aware axis labels
+ const PrivacyTick = ({
+ x,
+ y,
+ payload,
+ isRight,
+ }: {
+ x: number;
+ y: number;
+ payload: { value: string | number };
+ isRight?: boolean;
+ }) => {
+ const displayValue = isRight ? `$${payload.value}` : formatNumber(Number(payload.value));
+
+ return (
+
+ {displayValue}
+
+ );
+ };
+
return (
@@ -93,19 +126,17 @@ export function UsageTrendChart({
}
tickLine={false}
axisLine={{ className: 'stroke-muted' }}
- tickFormatter={(value) => formatNumber(value)}
/>
}
tickLine={false}
axisLine={{ className: 'stroke-muted' }}
- tickFormatter={(value) => `$${value}`}
/>
{label}
{payload.map((entry, index) => (
-
+
{entry.name}:{' '}
{entry.name === 'Tokens'
? formatNumber(Number(entry.value) || 0)
@@ -125,7 +160,12 @@ export function UsageTrendChart({
))}
{'requests' in tooltipData && (
-
+
Requests: {tooltipData.requests}
)}
diff --git a/ui/src/components/cliproxy-stats-overview.tsx b/ui/src/components/cliproxy-stats-overview.tsx
index b6ff362c..0e871eb9 100644
--- a/ui/src/components/cliproxy-stats-overview.tsx
+++ b/ui/src/components/cliproxy-stats-overview.tsx
@@ -8,6 +8,7 @@
* - Token usage with cost estimation
* - Model breakdown with usage distribution
* - Session history
+ * Respects privacy mode to blur sensitive data.
*/
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
@@ -26,12 +27,14 @@ import {
} from 'lucide-react';
import { cn } from '@/lib/utils';
import { useCliproxyStats, useCliproxyStatus } from '@/hooks/use-cliproxy-stats';
+import { usePrivacy, PRIVACY_BLUR_CLASS } from '@/contexts/privacy-context';
interface CliproxyStatsOverviewProps {
className?: string;
}
export function CliproxyStatsOverview({ className }: CliproxyStatsOverviewProps) {
+ const { privacyMode } = usePrivacy();
const { data: status, isLoading: statusLoading } = useCliproxyStatus();
const { data: stats, isLoading: statsLoading, error } = useCliproxyStats(status?.running);
@@ -216,8 +219,15 @@ export function CliproxyStatsOverview({ className }: CliproxyStatsOverviewProps)
Total Tokens
-
{formatNumber(totalTokens)}
-
+
+ {formatNumber(totalTokens)}
+
+
~${estimateCost(totalTokens).toFixed(2)} estimated
diff --git a/ui/src/pages/analytics.tsx b/ui/src/pages/analytics.tsx
index 3c3e9d64..520d385d 100644
--- a/ui/src/pages/analytics.tsx
+++ b/ui/src/pages/analytics.tsx
@@ -30,7 +30,8 @@ import {
useSessions,
type ModelUsage,
} from '@/hooks/use-usage';
-import { getModelColor } from '@/lib/utils';
+import { getModelColor, cn } from '@/lib/utils';
+import { usePrivacy, PRIVACY_BLUR_CLASS } from '@/contexts/privacy-context';
// Format token count to human-readable (K/M/B)
function formatTokens(num: number): string {
@@ -41,6 +42,8 @@ function formatTokens(num: number): string {
}
export function AnalyticsPage() {
+ const { privacyMode } = usePrivacy();
+
// Default to last 30 days
const [dateRange, setDateRange] = useState
({
from: subDays(new Date(), 30),
@@ -248,11 +251,21 @@ export function AnalyticsPage() {
{/* Token count */}
-
+
{formatTokens(model.tokens)}
{/* Total cost */}
-
+
${model.cost.toFixed(2)}