diff --git a/ui/src/components/analytics/anomaly-alert-badge.tsx b/ui/src/components/analytics/anomaly-alert-badge.tsx deleted file mode 100644 index b1eabb46..00000000 --- a/ui/src/components/analytics/anomaly-alert-badge.tsx +++ /dev/null @@ -1,128 +0,0 @@ -/** - * Anomaly Alert Badge Component - * - * Displays detected usage anomalies with visual indicators. - * Shows high input, I/O ratio, cost spikes, and cache read alerts. - */ - -import { useState } from 'react'; -import { Badge } from '@/components/ui/badge'; -import { Button } from '@/components/ui/button'; -import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover'; -import { AlertTriangle, ChevronDown, Zap, Gauge, DollarSign, Database } from 'lucide-react'; -import type { Anomaly, AnomalySummary, AnomalyType } from '@/hooks/use-usage'; -import { cn } from '@/lib/utils'; - -interface AnomalyAlertBadgeProps { - anomalies: Anomaly[]; - summary: AnomalySummary; - className?: string; -} - -const ANOMALY_CONFIG: Record< - AnomalyType, - { icon: React.ComponentType<{ className?: string }>; color: string; label: string } -> = { - high_input: { icon: Zap, color: 'text-yellow-600', label: 'High Input' }, - high_io_ratio: { icon: Gauge, color: 'text-orange-600', label: 'High I/O Ratio' }, - cost_spike: { icon: DollarSign, color: 'text-red-600', label: 'Cost Spike' }, - high_cache_read: { icon: Database, color: 'text-cyan-600', label: 'Heavy Caching' }, -}; - -export function AnomalyAlertBadge({ anomalies, summary, className }: AnomalyAlertBadgeProps) { - const [open, setOpen] = useState(false); - - if (summary.totalAnomalies === 0) { - return ( - - No anomalies - - ); - } - - // Get unique anomaly types for badges - const anomalyTypes = new Set(anomalies.map((a) => a.type)); - - return ( - - - - - -
-

- - Detected Anomalies -

-

- Unusual usage patterns detected in the selected period -

-
- - {/* Summary badges */} -
- {Array.from(anomalyTypes).map((type) => { - const config = ANOMALY_CONFIG[type]; - const Icon = config.icon; - const count = anomalies.filter((a) => a.type === type).length; - return ( - - - {count} {config.label} - - ); - })} -
- - {/* Anomaly list */} -
- {anomalies.slice(0, 10).map((anomaly, index) => { - const config = ANOMALY_CONFIG[anomaly.type]; - const Icon = config.icon; - - return ( -
-
- -
-
- {anomaly.date} - {anomaly.model && ( - - {truncateModel(anomaly.model)} - - )} -
-

{anomaly.message}

-
-
-
- ); - })} - {anomalies.length > 10 && ( -
- +{anomalies.length - 10} more anomalies -
- )} -
-
-
- ); -} - -function truncateModel(model: string): string { - if (model.length <= 20) return model; - // Try to extract the meaningful part - const parts = model.split('-'); - if (parts.length >= 3) { - return parts.slice(0, 3).join('-') + '...'; - } - return model.slice(0, 17) + '...'; -} diff --git a/ui/src/components/analytics/usage-insights-card.tsx b/ui/src/components/analytics/usage-insights-card.tsx new file mode 100644 index 00000000..b7b48585 --- /dev/null +++ b/ui/src/components/analytics/usage-insights-card.tsx @@ -0,0 +1,168 @@ +import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; +import { Badge } from '@/components/ui/badge'; +import { ScrollArea } from '@/components/ui/scroll-area'; +import { CheckCircle2, Zap, Gauge, DollarSign, Database, Lightbulb } from 'lucide-react'; +import type { Anomaly, AnomalySummary, AnomalyType } from '@/hooks/use-usage'; +import { cn } from '@/lib/utils'; + +interface UsageInsightsCardProps { + anomalies?: Anomaly[]; + summary?: AnomalySummary; + isLoading?: boolean; + className?: string; +} + +const ANOMALY_CONFIG: Record< + AnomalyType, + { + icon: React.ComponentType<{ className?: string }>; + color: string; + label: string; + description: string; + } +> = { + high_input: { + icon: Zap, + color: 'text-yellow-600 dark:text-yellow-400', + label: 'High Input', + description: 'Unusually high input token usage detected.', + }, + high_io_ratio: { + icon: Gauge, + color: 'text-orange-600 dark:text-orange-400', + label: 'High I/O Ratio', + description: 'Output tokens are significantly higher than input tokens.', + }, + cost_spike: { + icon: DollarSign, + color: 'text-red-600 dark:text-red-400', + label: 'Cost Spike', + description: 'Daily cost is significantly higher than average.', + }, + high_cache_read: { + icon: Database, + color: 'text-cyan-600 dark:text-cyan-400', + label: 'Heavy Caching', + description: 'High volume of cache read operations.', + }, +}; + +export function UsageInsightsCard({ + anomalies = [], + summary, + isLoading, + className, +}: UsageInsightsCardProps) { + if (isLoading) { + return ( + + + + + Usage Insights + + + +
+
+
+
+ + + ); + } + + const hasAnomalies = summary && summary.totalAnomalies > 0; + + return ( + + +
+ + + Usage Insights + + {hasAnomalies ? ( + + Attention Needed + + ) : ( + + Healthy + + )} +
+
+ + + {hasAnomalies ? ( + +
+ {anomalies.map((anomaly, index) => { + const config = ANOMALY_CONFIG[anomaly.type]; + const Icon = config.icon; + + return ( +
+
+
+ +
+
+
+

{config.label}

+ + {anomaly.date} + +
+

+ {anomaly.message} +

+ {anomaly.model && ( +
+ + {anomaly.model} + +
+ )} +
+
+
+ ); + })} +
+
+ ) : ( +
+
+ +
+

No anomalies detected

+

+ Your usage patterns look normal for the selected period. +

+
+ )} +
+
+ ); +} diff --git a/ui/src/pages/analytics.tsx b/ui/src/pages/analytics.tsx index 0605c248..f0f03dc1 100644 --- a/ui/src/pages/analytics.tsx +++ b/ui/src/pages/analytics.tsx @@ -18,7 +18,7 @@ import { UsageTrendChart } from '@/components/analytics/usage-trend-chart'; import { ModelBreakdownChart } from '@/components/analytics/model-breakdown-chart'; import { ModelDetailsContent } from '@/components/analytics/model-details-content'; import { SessionStatsCard } from '@/components/analytics/session-stats-card'; -import { AnomalyAlertBadge } from '@/components/analytics/anomaly-alert-badge'; +import { UsageInsightsCard } from '@/components/analytics/usage-insights-card'; import { TrendingUp, PieChart, RefreshCw, DollarSign, ChevronRight } from 'lucide-react'; import { useUsageSummary, @@ -105,10 +105,6 @@ export function AnalyticsPage() {

Track usage & insights

- {/* Anomaly Alert Badge */} - {!isInsightsLoading && insights && ( - - )} - {/* Session Stats - 4/10 width */} + {/* Session Stats - 2/10 width */} + + {/* Usage Insights - 2/10 width */} +