File size: 4,885 Bytes
c2ea5ed
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
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`;
}