Spaces:
Running
Running
| import { useState, useEffect, useCallback, useMemo } from "react"; | |
| import { useAgentGraph } from "@/context/AgentGraphContext"; | |
| interface DashboardStats { | |
| totalTraces: number; | |
| totalAgentGraphs: number; | |
| totalEntities: number; | |
| totalRelations: number; | |
| completedProcessing: number; | |
| processingProgress: number; | |
| systemStatus: "healthy" | "warning" | "error"; | |
| } | |
| interface ActivityItem { | |
| id: string; | |
| action: string; | |
| trace: string; | |
| timestamp: string; | |
| status: "success" | "error" | "processing"; | |
| } | |
| export function useDashboardData() { | |
| const { state } = useAgentGraph(); | |
| const [isLoading, setIsLoading] = useState(true); | |
| const [error, setError] = useState<string | null>(null); | |
| // Calculate stats using useMemo to avoid unnecessary recalculations | |
| const stats = useMemo<DashboardStats>(() => { | |
| const totalTraces = state.traces.length; | |
| const totalAgentGraphs = state.traces.reduce((total, trace) => { | |
| // Count only final knowledge graphs (consistent with sidebar and main content) | |
| const finalKGs = | |
| trace.knowledge_graphs?.filter( | |
| (kg) => | |
| kg.is_final === true || | |
| (kg.window_index === null && kg.window_total !== null) | |
| ) || []; | |
| return total + finalKGs.length; | |
| }, 0); | |
| const totalEntities = state.traces.reduce((total, trace) => { | |
| const entityCount = | |
| trace.knowledge_graphs?.reduce((kgTotal, kg) => { | |
| return kgTotal + (kg.entity_count || 0); | |
| }, 0) || 0; | |
| return total + entityCount; | |
| }, 0); | |
| const totalRelations = state.traces.reduce((total, trace) => { | |
| const relationCount = | |
| trace.knowledge_graphs?.reduce((kgTotal, kg) => { | |
| return kgTotal + (kg.relation_count || 0); | |
| }, 0) || 0; | |
| return total + relationCount; | |
| }, 0); | |
| const completedProcessing = state.traces.reduce((total, trace) => { | |
| const completedKGs = | |
| trace.knowledge_graphs?.filter((kg) => kg.status === "analyzed") || []; | |
| return total + completedKGs.length; | |
| }, 0); | |
| const processingProgress = | |
| totalAgentGraphs > 0 ? (completedProcessing / totalAgentGraphs) * 100 : 0; | |
| return { | |
| totalTraces, | |
| totalAgentGraphs, | |
| totalEntities, | |
| totalRelations, | |
| completedProcessing, | |
| processingProgress, | |
| systemStatus: "healthy", | |
| }; | |
| }, [state.traces]); | |
| // Calculate recent activity using useMemo | |
| const recentActivity = useMemo<ActivityItem[]>(() => { | |
| const activity: ActivityItem[] = []; | |
| // Add recent trace uploads | |
| state.traces.slice(0, 2).forEach((trace) => { | |
| activity.push({ | |
| id: `trace-${trace.id}`, | |
| action: "Trace Uploaded", | |
| trace: trace.filename, | |
| timestamp: getRelativeTime( | |
| trace.update_timestamp || trace.upload_timestamp || "" | |
| ), | |
| status: "success", | |
| }); | |
| }); | |
| // Add recent graph generations from traces | |
| const allKnowledgeGraphs = state.traces.flatMap( | |
| (trace) => trace.knowledge_graphs || [] | |
| ); | |
| allKnowledgeGraphs | |
| .sort((a, b) => { | |
| const dateA = new Date(a.created_at || 0); | |
| const dateB = new Date(b.created_at || 0); | |
| return dateB.getTime() - dateA.getTime(); | |
| }) | |
| .slice(0, 2) | |
| .forEach((kg, index) => { | |
| activity.push({ | |
| id: `kg-${kg.kg_id || kg.id || index}`, | |
| action: "Agent Graph Generated", | |
| trace: kg.filename || "Unknown", | |
| timestamp: getRelativeTime(kg.created_at), | |
| status: kg.status === "analyzed" ? "success" : "processing", | |
| }); | |
| }); | |
| return activity.slice(0, 5); | |
| }, [state.traces]); | |
| const fetchDashboardData = useCallback(async () => { | |
| try { | |
| setIsLoading(true); | |
| setError(null); | |
| // Data is now calculated via useMemo, so we just need to handle loading state | |
| setIsLoading(false); | |
| } catch (err) { | |
| setError( | |
| err instanceof Error ? err.message : "Failed to load dashboard data" | |
| ); | |
| setIsLoading(false); | |
| } | |
| }, []); | |
| useEffect(() => { | |
| fetchDashboardData(); | |
| }, [fetchDashboardData]); | |
| return { | |
| stats, | |
| recentActivity, | |
| isLoading, | |
| error, | |
| refresh: fetchDashboardData, | |
| }; | |
| } | |
| function getRelativeTime(dateString: string): string { | |
| const date = new Date(dateString); | |
| const now = new Date(); | |
| const diffMs = now.getTime() - date.getTime(); | |
| // If the date is invalid, return a default | |
| if (isNaN(diffMs)) return "Recently"; | |
| const diffMins = Math.floor(diffMs / 60000); | |
| const diffHours = Math.floor(diffMs / 3600000); | |
| const diffDays = Math.floor(diffMs / 86400000); | |
| if (diffMins < 1) return "Just now"; | |
| if (diffMins < 60) return `${diffMins} minute${diffMins > 1 ? "s" : ""} ago`; | |
| if (diffHours < 24) return `${diffHours} hour${diffHours > 1 ? "s" : ""} ago`; | |
| return `${diffDays} day${diffDays > 1 ? "s" : ""} ago`; | |
| } | |