File size: 4,573 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 React, { useMemo } from "react";
import { useAgentGraph } from "@/context/AgentGraphContext";

interface EntityRelationTreeChartProps {
  className?: string;
}

interface ChartData {
  name: string;
  count: number;
  color: string;
  type: "entity" | "relation";
}

export function EntityRelationTreeChart({
  className,
}: EntityRelationTreeChartProps) {
  const { state } = useAgentGraph();

  // Process data to get entity and relation type distributions
  const chartData = useMemo(() => {
    const entityTypes = new Map<string, number>();
    const relationTypes = new Map<string, number>();

    state.traces.forEach((trace) => {
      trace.knowledge_graphs?.forEach((kg: any) => {
        if (kg.graph_data) {
          try {
            const graphData =
              typeof kg.graph_data === "string"
                ? JSON.parse(kg.graph_data)
                : kg.graph_data;

            // Count entity types
            if (graphData.entities && Array.isArray(graphData.entities)) {
              graphData.entities.forEach((entity: any) => {
                const type = entity.type || "Unknown";
                entityTypes.set(type, (entityTypes.get(type) || 0) + 1);
              });
            }

            // Count relation types
            if (graphData.relations && Array.isArray(graphData.relations)) {
              graphData.relations.forEach((relation: any) => {
                const type = relation.type || "Unknown";
                relationTypes.set(type, (relationTypes.get(type) || 0) + 1);
              });
            }
          } catch (error) {
            console.warn("Error parsing graph_data:", error);
          }
        }
      });
    });

    // Entity type colors
    const entityColors = {
      Agent: "#3b82f6",
      Task: "#10b981",
      Tool: "#f59e0b",
      Input: "#8b5cf6",
      Output: "#ef4444",
      Human: "#06b6d4",
      Unknown: "#6b7280",
    };

    // Relation type colors (using different shades)
    const relationColors = {
      CONSUMED_BY: "#93c5fd",
      PERFORMS: "#86efac",
      ASSIGNED_TO: "#fbbf24",
      USES: "#c4b5fd",
      REQUIRED_BY: "#fca5a5",
      SUBTASK_OF: "#67e8f9",
      NEXT: "#a78bfa",
      PRODUCES: "#34d399",
      DELIVERS_TO: "#60a5fa",
      INTERVENES: "#fb7185",
      Unknown: "#9ca3af",
    };

    const data: ChartData[] = [];

    // Add entity type data
    entityTypes.forEach((count, type) => {
      data.push({
        name: type,
        count,
        color:
          entityColors[type as keyof typeof entityColors] ||
          entityColors.Unknown,
        type: "entity",
      });
    });

    // Add relation type data
    relationTypes.forEach((count, type) => {
      data.push({
        name: type.replace(/_/g, " "), // Make relation names more readable
        count,
        color:
          relationColors[type as keyof typeof relationColors] ||
          relationColors.Unknown,
        type: "relation",
      });
    });

    // Sort by count descending and take top 8 to fit in the space
    return data.sort((a, b) => b.count - a.count).slice(0, 8);
  }, [state.traces]);

  if (chartData.length === 0) {
    return (
      <div
        className={`h-20 w-full flex items-center justify-center ${className}`}
      >
        <div className="text-xs text-muted-foreground/50">No graph data</div>
      </div>
    );
  }

  // Calculate max count for scaling
  const maxCount = Math.max(...chartData.map((d) => d.count));

  return (
    <div
      className={`h-20 w-full flex items-end justify-center gap-1 px-2 ${className}`}
    >
      {chartData.map((item, index) => {
        const height = Math.max(8, (item.count / maxCount) * 60); // Min height 8px, max 60px

        return (
          <div
            key={`${item.type}-${item.name}-${index}`}
            className="flex flex-col items-center justify-end flex-1 min-w-0"
            title={`${item.name}: ${item.count}`}
          >
            {/* Bar */}
            <div
              className="w-full rounded-t-sm transition-all duration-300 hover:opacity-80"
              style={{
                height: `${height}px`,
                backgroundColor: item.color,
                boxShadow: `0 0 4px ${item.color}40`,
              }}
            />

            {/* Label */}
            <div className="text-[8px] text-muted-foreground/70 mt-1 truncate w-full text-center font-medium">
              {item.name.length > 6 ? `${item.name.slice(0, 5)}...` : item.name}
            </div>
          </div>
        );
      })}
    </div>
  );
}