Skip to content

Commit 824c3ba

Browse files
committed
feat(ui): replace anomaly alert badge with usage insights card
1 parent aa1e932 commit 824c3ba

File tree

3 files changed

+179
-135
lines changed

3 files changed

+179
-135
lines changed

ui/src/components/analytics/anomaly-alert-badge.tsx

Lines changed: 0 additions & 128 deletions
This file was deleted.
Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
2+
import { Badge } from '@/components/ui/badge';
3+
import { ScrollArea } from '@/components/ui/scroll-area';
4+
import { CheckCircle2, Zap, Gauge, DollarSign, Database, Lightbulb } from 'lucide-react';
5+
import type { Anomaly, AnomalySummary, AnomalyType } from '@/hooks/use-usage';
6+
import { cn } from '@/lib/utils';
7+
8+
interface UsageInsightsCardProps {
9+
anomalies?: Anomaly[];
10+
summary?: AnomalySummary;
11+
isLoading?: boolean;
12+
className?: string;
13+
}
14+
15+
const ANOMALY_CONFIG: Record<
16+
AnomalyType,
17+
{
18+
icon: React.ComponentType<{ className?: string }>;
19+
color: string;
20+
label: string;
21+
description: string;
22+
}
23+
> = {
24+
high_input: {
25+
icon: Zap,
26+
color: 'text-yellow-600 dark:text-yellow-400',
27+
label: 'High Input',
28+
description: 'Unusually high input token usage detected.',
29+
},
30+
high_io_ratio: {
31+
icon: Gauge,
32+
color: 'text-orange-600 dark:text-orange-400',
33+
label: 'High I/O Ratio',
34+
description: 'Output tokens are significantly higher than input tokens.',
35+
},
36+
cost_spike: {
37+
icon: DollarSign,
38+
color: 'text-red-600 dark:text-red-400',
39+
label: 'Cost Spike',
40+
description: 'Daily cost is significantly higher than average.',
41+
},
42+
high_cache_read: {
43+
icon: Database,
44+
color: 'text-cyan-600 dark:text-cyan-400',
45+
label: 'Heavy Caching',
46+
description: 'High volume of cache read operations.',
47+
},
48+
};
49+
50+
export function UsageInsightsCard({
51+
anomalies = [],
52+
summary,
53+
isLoading,
54+
className,
55+
}: UsageInsightsCardProps) {
56+
if (isLoading) {
57+
return (
58+
<Card className={cn('flex flex-col h-full', className)}>
59+
<CardHeader className="px-4 py-3 border-b">
60+
<CardTitle className="text-base font-semibold flex items-center gap-2">
61+
<Lightbulb className="w-4 h-4 text-muted-foreground" />
62+
Usage Insights
63+
</CardTitle>
64+
</CardHeader>
65+
<CardContent className="p-4 flex-1 flex items-center justify-center">
66+
<div className="animate-pulse flex flex-col items-center gap-2 opacity-50">
67+
<div className="h-8 w-8 bg-muted rounded-full" />
68+
<div className="h-4 w-32 bg-muted rounded" />
69+
</div>
70+
</CardContent>
71+
</Card>
72+
);
73+
}
74+
75+
const hasAnomalies = summary && summary.totalAnomalies > 0;
76+
77+
return (
78+
<Card className={cn('flex flex-col h-full overflow-hidden', className)}>
79+
<CardHeader className="px-4 py-3 border-b bg-muted/5">
80+
<div className="flex items-center justify-between">
81+
<CardTitle className="text-base font-semibold flex items-center gap-2">
82+
<Lightbulb
83+
className={cn('w-4 h-4', hasAnomalies ? 'text-amber-500' : 'text-green-500')}
84+
/>
85+
Usage Insights
86+
</CardTitle>
87+
{hasAnomalies ? (
88+
<Badge
89+
variant="destructive"
90+
className="h-5 px-1.5 text-[10px] uppercase font-bold tracking-wider"
91+
>
92+
Attention Needed
93+
</Badge>
94+
) : (
95+
<Badge
96+
variant="outline"
97+
className="h-5 px-1.5 text-[10px] uppercase font-bold tracking-wider text-green-600 border-green-200 bg-green-50 dark:bg-green-900/10 dark:border-green-800"
98+
>
99+
Healthy
100+
</Badge>
101+
)}
102+
</div>
103+
</CardHeader>
104+
105+
<CardContent className="p-0 flex-1 min-h-0 flex flex-col">
106+
{hasAnomalies ? (
107+
<ScrollArea className="flex-1">
108+
<div className="divide-y">
109+
{anomalies.map((anomaly, index) => {
110+
const config = ANOMALY_CONFIG[anomaly.type];
111+
const Icon = config.icon;
112+
113+
return (
114+
<div key={index} className="p-4 hover:bg-muted/50 transition-colors">
115+
<div className="flex items-start gap-3">
116+
<div
117+
className={cn(
118+
'p-2 rounded-lg shrink-0 bg-muted/30',
119+
config.color
120+
.replace('text-', 'bg-')
121+
.replace('600', '100')
122+
.replace('400', '900/20')
123+
)}
124+
>
125+
<Icon className={cn('h-4 w-4', config.color)} />
126+
</div>
127+
<div className="flex-1 min-w-0 space-y-1">
128+
<div className="flex items-center justify-between gap-2">
129+
<p className="font-medium text-sm">{config.label}</p>
130+
<span className="text-xs text-muted-foreground whitespace-nowrap">
131+
{anomaly.date}
132+
</span>
133+
</div>
134+
<p className="text-xs text-muted-foreground line-clamp-2">
135+
{anomaly.message}
136+
</p>
137+
{anomaly.model && (
138+
<div className="pt-1">
139+
<Badge
140+
variant="secondary"
141+
className="text-[10px] px-1 py-0 h-5 font-mono"
142+
>
143+
{anomaly.model}
144+
</Badge>
145+
</div>
146+
)}
147+
</div>
148+
</div>
149+
</div>
150+
);
151+
})}
152+
</div>
153+
</ScrollArea>
154+
) : (
155+
<div className="flex-1 flex flex-col items-center justify-center p-6 text-center text-muted-foreground">
156+
<div className="w-12 h-12 rounded-full bg-green-100 dark:bg-green-900/20 flex items-center justify-center mb-3">
157+
<CheckCircle2 className="w-6 h-6 text-green-600 dark:text-green-400" />
158+
</div>
159+
<p className="font-medium text-foreground">No anomalies detected</p>
160+
<p className="text-xs mt-1 max-w-[200px]">
161+
Your usage patterns look normal for the selected period.
162+
</p>
163+
</div>
164+
)}
165+
</CardContent>
166+
</Card>
167+
);
168+
}

ui/src/pages/analytics.tsx

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import { UsageTrendChart } from '@/components/analytics/usage-trend-chart';
1818
import { ModelBreakdownChart } from '@/components/analytics/model-breakdown-chart';
1919
import { ModelDetailsContent } from '@/components/analytics/model-details-content';
2020
import { SessionStatsCard } from '@/components/analytics/session-stats-card';
21-
import { AnomalyAlertBadge } from '@/components/analytics/anomaly-alert-badge';
21+
import { UsageInsightsCard } from '@/components/analytics/usage-insights-card';
2222
import { TrendingUp, PieChart, RefreshCw, DollarSign, ChevronRight } from 'lucide-react';
2323
import {
2424
useUsageSummary,
@@ -105,10 +105,6 @@ export function AnalyticsPage() {
105105
<p className="text-sm text-muted-foreground">Track usage & insights</p>
106106
</div>
107107
<div className="flex items-center gap-2">
108-
{/* Anomaly Alert Badge */}
109-
{!isInsightsLoading && insights && (
110-
<AnomalyAlertBadge anomalies={insights.anomalies} summary={insights.summary} />
111-
)}
112108
<DateRangeFilter
113109
value={dateRange}
114110
onChange={setDateRange}
@@ -289,11 +285,19 @@ export function AnalyticsPage() {
289285
</CardContent>
290286
</Card>
291287

292-
{/* Session Stats - 4/10 width */}
288+
{/* Session Stats - 2/10 width */}
293289
<SessionStatsCard
294290
data={sessions}
295291
isLoading={isSessionsLoading}
296-
className="lg:col-span-4"
292+
className="lg:col-span-2"
293+
/>
294+
295+
{/* Usage Insights - 2/10 width */}
296+
<UsageInsightsCard
297+
anomalies={insights?.anomalies}
298+
summary={insights?.summary}
299+
isLoading={isInsightsLoading}
300+
className="lg:col-span-2"
297301
/>
298302
</div>
299303

0 commit comments

Comments
 (0)