diff --git a/ui/src/components/analytics/cache-efficiency-card.tsx b/ui/src/components/analytics/cache-efficiency-card.tsx index eb04bff4..c079c659 100644 --- a/ui/src/components/analytics/cache-efficiency-card.tsx +++ b/ui/src/components/analytics/cache-efficiency-card.tsx @@ -2,7 +2,7 @@ * Cache Efficiency Card Component * * Displays cache usage metrics including hit rate, savings estimate, - * and cache read/write breakdown. + * and cache read/write breakdown. Respects privacy mode to blur sensitive data. */ import { useMemo } from 'react'; @@ -11,6 +11,7 @@ import { Skeleton } from '@/components/ui/skeleton'; import { Database, TrendingUp, Zap } from 'lucide-react'; import type { UsageSummary } from '@/hooks/use-usage'; import { cn } from '@/lib/utils'; +import { usePrivacy, PRIVACY_BLUR_CLASS } from '@/contexts/privacy-context'; interface CacheEfficiencyCardProps { data: UsageSummary | undefined; @@ -19,6 +20,8 @@ interface CacheEfficiencyCardProps { } export function CacheEfficiencyCard({ data, isLoading, className }: CacheEfficiencyCardProps) { + const { privacyMode } = usePrivacy(); + const metrics = useMemo(() => { if (!data) return null; @@ -93,7 +96,9 @@ export function CacheEfficiencyCard({ data, isLoading, className }: CacheEfficie
- ${metrics.estimatedSavings.toFixed(2)} + + ${metrics.estimatedSavings.toFixed(2)} +

Estimated Savings @@ -106,21 +111,30 @@ export function CacheEfficiencyCard({ data, isLoading, className }: CacheEfficie

- {metrics.cacheHitRate.toFixed(0)}% + + {metrics.cacheHitRate.toFixed(0)}% +

Hit Rate

{/* Cache Cost */}
- ${metrics.cacheCost.toFixed(2)} + + ${metrics.cacheCost.toFixed(2)} +

Cache Cost

{/* Cache breakdown bar */}
-
+
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)}