Spaces:
Sleeping
Sleeping
| import React, { useState, useEffect, useCallback, useMemo } from "react"; | |
| import { useDashboardData } from "@/hooks/useDashboardData"; | |
| import { DashboardVisualization } from "../dashboard/DashboardVisualization"; | |
| import { EntityRelationAnalysisModal } from "../dashboard/modals/EntityRelationAnalysisModal"; | |
| import { TinyTrendChart } from "@/components/shared/TinyTrendChart"; | |
| import { EntityRelationTreeChart } from "@/components/shared/EntityRelationTreeChart"; | |
| import { | |
| generateTrendData, | |
| calculateFailureCount, | |
| } from "@/lib/trend-data-generator"; | |
| import { useAgentGraph } from "@/context/AgentGraphContext"; | |
| import { api } from "@/lib/api"; | |
| import { | |
| Card, | |
| CardContent, | |
| CardDescription, | |
| CardHeader, | |
| CardTitle, | |
| } from "@/components/ui/card"; | |
| import { Badge } from "@/components/ui/badge"; | |
| import { Button } from "@/components/ui/button"; | |
| import { Skeleton } from "@/components/ui/skeleton"; | |
| import { | |
| FileText, | |
| AlertCircle, | |
| Clock, | |
| Users, | |
| AlertTriangle, | |
| } from "lucide-react"; | |
| export function DashboardView() { | |
| const { state, actions } = useAgentGraph(); | |
| const { stats, recentActivity, isLoading, error, refresh } = | |
| useDashboardData(); | |
| // Modal states | |
| const [isEntityModalOpen, setIsEntityModalOpen] = useState(false); | |
| // Generate realistic trend data based on current traces | |
| const trendData = useMemo( | |
| () => generateTrendData(state.traces), | |
| [state.traces] | |
| ); | |
| // Load traces data when dashboard mounts (if not already loaded) | |
| const loadTracesData = useCallback(async () => { | |
| if (state.traces.length === 0 && !state.isLoading) { | |
| actions.setLoading(true); | |
| try { | |
| const tracesData = await api.traces.list(); | |
| actions.setTraces(Array.isArray(tracesData) ? tracesData : []); | |
| } catch (error) { | |
| actions.setError( | |
| error instanceof Error ? error.message : "Failed to load traces" | |
| ); | |
| actions.setTraces([]); | |
| } finally { | |
| actions.setLoading(false); | |
| } | |
| } | |
| }, [state.traces.length, state.isLoading, actions]); | |
| useEffect(() => { | |
| loadTracesData(); | |
| }, [loadTracesData]); | |
| const handleUploadTrace = () => { | |
| actions.setActiveView("upload"); | |
| }; | |
| return ( | |
| <div className="flex-1 flex flex-col p-6 gap-6 min-h-0 relative"> | |
| {/* Glassmorphism background gradient */} | |
| <div className="absolute inset-0 bg-gradient-to-br from-blue-50/30 via-purple-50/20 to-green-50/30 dark:from-blue-950/20 dark:via-purple-950/10 dark:to-green-950/20 pointer-events-none" /> | |
| {/* Content with relative positioning */} | |
| <div className="relative z-10 flex flex-col gap-6 h-full"> | |
| {/* Error State */} | |
| {error && ( | |
| <Card className="border-destructive/50 bg-destructive/10"> | |
| <CardContent className="p-4"> | |
| <div className="flex items-center gap-2 text-destructive"> | |
| <AlertCircle className="h-4 w-4" /> | |
| <span className="text-sm">{error}</span> | |
| <Button | |
| variant="outline" | |
| size="sm" | |
| onClick={refresh} | |
| className="ml-auto" | |
| > | |
| Try Again | |
| </Button> | |
| </div> | |
| </CardContent> | |
| </Card> | |
| )} | |
| {/* Stats Grid */} | |
| <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-3"> | |
| {/* Traces & Agent Graphs - Blue & Green Theme */} | |
| <div className="glass-card glass-card-blue rounded-2xl p-4 cursor-pointer group"> | |
| <div className="flex items-center justify-between mb-3"> | |
| <div className="flex items-center gap-3"> | |
| <div className="glass-icon rounded-xl p-2"> | |
| <FileText className="h-5 w-5 text-blue-500" /> | |
| </div> | |
| <div> | |
| <h3 className="text-sm font-medium text-foreground/90"> | |
| Traces & Graphs | |
| </h3> | |
| {isLoading ? ( | |
| <div className="flex gap-2"> | |
| <Skeleton className="h-6 w-8 mt-1" /> | |
| <span className="text-xs text-muted-foreground mt-2"> | |
| • | |
| </span> | |
| <Skeleton className="h-6 w-8 mt-1" /> | |
| </div> | |
| ) : ( | |
| <div className="flex items-center gap-2"> | |
| <div className="text-xl font-bold glass-number text-blue-600 dark:text-blue-400"> | |
| {stats.totalTraces} | |
| </div> | |
| <span className="text-xs text-muted-foreground">•</span> | |
| <div className="text-xl font-bold glass-number text-green-600 dark:text-green-400"> | |
| {stats.totalAgentGraphs} | |
| </div> | |
| </div> | |
| )} | |
| </div> | |
| </div> | |
| </div> | |
| {/* Dual-line trend chart */} | |
| <div className="mb-2"> | |
| <TinyTrendChart | |
| data={trendData.tracesAndGraphs} | |
| color="#3b82f6" | |
| color2="#22c55e" | |
| isDual={true} | |
| /> | |
| </div> | |
| {stats.totalTraces > 0 ? ( | |
| <p className="text-xs text-muted-foreground"> | |
| <span className="text-blue-600">Traces</span> ready •{" "} | |
| <span className="text-green-600">Graphs</span> generated | |
| </p> | |
| ) : ( | |
| <Button | |
| variant="link" | |
| size="sm" | |
| onClick={handleUploadTrace} | |
| className="text-xs text-blue-600 hover:text-blue-500 p-0 h-auto font-normal" | |
| > | |
| Upload your first trace | |
| </Button> | |
| )} | |
| </div> | |
| {/* Entities & Relations - Purple & Orange Theme */} | |
| <div | |
| className="glass-card glass-card-purple rounded-2xl p-4 cursor-pointer group hover:scale-105 transition-all duration-300" | |
| onClick={() => setIsEntityModalOpen(true)} | |
| > | |
| <div className="flex items-center justify-between mb-3"> | |
| <div className="flex items-center gap-3"> | |
| <div className="glass-icon rounded-xl p-2"> | |
| <Users className="h-5 w-5 text-purple-500" /> | |
| </div> | |
| <div> | |
| <h3 className="text-sm font-medium text-foreground/90"> | |
| Entities & Relations | |
| </h3> | |
| {isLoading ? ( | |
| <div className="flex gap-2"> | |
| <Skeleton className="h-6 w-12 mt-1" /> | |
| <span className="text-xs text-muted-foreground mt-2"> | |
| • | |
| </span> | |
| <Skeleton className="h-6 w-12 mt-1" /> | |
| </div> | |
| ) : ( | |
| <div className="flex items-center gap-2"> | |
| <div className="text-xl font-bold glass-number text-purple-600 dark:text-purple-400"> | |
| {stats.totalEntities} | |
| </div> | |
| <span className="text-xs text-muted-foreground">•</span> | |
| <div className="text-xl font-bold glass-number text-orange-600 dark:text-orange-400"> | |
| {stats.totalRelations} | |
| </div> | |
| </div> | |
| )} | |
| </div> | |
| </div> | |
| </div> | |
| {/* Entity-Relation Tree Visualization */} | |
| <div className="mb-2"> | |
| <EntityRelationTreeChart /> | |
| </div> | |
| <p className="text-xs text-muted-foreground"> | |
| <span className="text-purple-600">Entities</span> extracted •{" "} | |
| <span className="text-orange-600">Relations</span> connected | |
| </p> | |
| </div> | |
| {/* Failures - Red Theme */} | |
| <div className="glass-card-red rounded-2xl p-4 cursor-pointer group backdrop-blur-md"> | |
| <div className="flex items-center justify-between mb-3"> | |
| <div className="flex items-center gap-3"> | |
| <div className="glass-icon rounded-xl p-2 bg-red-500/10"> | |
| <AlertTriangle className="h-5 w-5 text-red-500" /> | |
| </div> | |
| <div> | |
| <h3 className="text-sm font-medium text-foreground/90"> | |
| System Failures | |
| </h3> | |
| {isLoading ? ( | |
| <Skeleton className="h-6 w-12 mt-1" /> | |
| ) : ( | |
| <div className="text-xl font-bold glass-number text-red-600 dark:text-red-400"> | |
| {calculateFailureCount(state.traces)} | |
| </div> | |
| )} | |
| </div> | |
| </div> | |
| </div> | |
| {/* Failures trend chart */} | |
| <div className="mb-2"> | |
| <TinyTrendChart data={trendData.failures} color="#dc2626" /> | |
| </div> | |
| <p className="text-xs text-muted-foreground"> | |
| Processing errors tracked | |
| </p> | |
| </div> | |
| </div> | |
| {/* Dashboard Visualization */} | |
| <DashboardVisualization className="mt-6" /> | |
| {/* Main Content Area - Recent Activity */} | |
| <div className="flex-1 flex flex-col gap-6 min-h-0 overflow-auto"> | |
| {/* Recent Activity - Now takes full available space */} | |
| <Card className="flex-1 flex flex-col min-h-0"> | |
| <CardHeader className="pb-4"> | |
| <CardTitle className="flex items-center gap-2"> | |
| <Clock className="h-4 w-4" /> | |
| Recent Activity | |
| </CardTitle> | |
| <CardDescription> | |
| Latest system events and processing updates | |
| </CardDescription> | |
| </CardHeader> | |
| <CardContent className="flex-1 overflow-auto"> | |
| {isLoading ? ( | |
| <div className="space-y-4"> | |
| {[...Array(3)].map((_, i) => ( | |
| <div key={i} className="flex items-center justify-between"> | |
| <div className="space-y-2"> | |
| <Skeleton className="h-4 w-32" /> | |
| <Skeleton className="h-3 w-24" /> | |
| </div> | |
| <Skeleton className="h-6 w-16" /> | |
| </div> | |
| ))} | |
| </div> | |
| ) : recentActivity.length > 0 ? ( | |
| <div className="space-y-4"> | |
| {recentActivity.map((activity) => ( | |
| <div | |
| key={activity.id} | |
| className="flex items-center justify-between" | |
| > | |
| <div className="space-y-1"> | |
| <p className="text-sm font-medium">{activity.action}</p> | |
| <p className="text-xs text-muted-foreground"> | |
| {activity.trace} | |
| </p> | |
| </div> | |
| <div className="text-right space-y-1"> | |
| <Badge | |
| variant={ | |
| activity.status === "success" | |
| ? "default" | |
| : activity.status === "error" | |
| ? "destructive" | |
| : activity.status === "processing" | |
| ? "secondary" | |
| : "secondary" | |
| } | |
| className={`text-xs ${ | |
| activity.status === "success" | |
| ? "bg-green-100 text-green-800 border-green-200 dark:bg-green-900 dark:text-green-300" | |
| : activity.status === "error" | |
| ? "bg-red-100 text-red-800 border-red-200 dark:bg-red-900 dark:text-red-300" | |
| : activity.status === "processing" | |
| ? "bg-blue-100 text-blue-800 border-blue-200 dark:bg-blue-900 dark:text-blue-300" | |
| : "bg-gray-100 text-gray-800 border-gray-200 dark:bg-gray-900 dark:text-gray-300" | |
| }`} | |
| > | |
| {activity.status} | |
| </Badge> | |
| <p className="text-xs text-muted-foreground"> | |
| {activity.timestamp} | |
| </p> | |
| </div> | |
| </div> | |
| ))} | |
| </div> | |
| ) : ( | |
| <div className="text-center py-8"> | |
| <Clock className="h-8 w-8 mx-auto mb-2 text-muted-foreground" /> | |
| <p className="text-sm text-muted-foreground"> | |
| No recent activity | |
| </p> | |
| </div> | |
| )} | |
| </CardContent> | |
| </Card> | |
| </div> | |
| {/* Analysis Modals */} | |
| <EntityRelationAnalysisModal | |
| open={isEntityModalOpen} | |
| onOpenChange={setIsEntityModalOpen} | |
| /> | |
| </div> | |
| </div> | |
| ); | |
| } | |