import React, { useState } from "react"; import { Button } from "@/components/ui/button"; import { Badge } from "@/components/ui/badge"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Collapsible, CollapsibleContent } from "@/components/ui/collapsible"; import { ChevronDown, ChevronUp, Eye, Play, FileText, BarChart3, Trash2, Activity, Settings, Scissors, } from "lucide-react"; import { KnowledgeGraph } from "@/types"; import { useAgentGraph } from "@/context/AgentGraphContext"; interface KnowledgeGraphTreeProps { knowledgeGraphs: KnowledgeGraph[]; selectedKgId?: string; onSelectKg?: (kgId: string) => void; _onViewKg?: (kgId: string) => void; onDeleteKg?: (kgId: string, name: string) => void; onReplayKg?: ( kgId: string, traceId: string, processingRunId?: string ) => void; onViewSegment?: ( traceId: string, startChar: number, endChar: number, windowIndex: number ) => void; currentTraceId?: string; } export const KnowledgeGraphTree: React.FC = ({ knowledgeGraphs, selectedKgId: _selectedKgId, onSelectKg: _onSelectKg, _onViewKg, onDeleteKg, onReplayKg, onViewSegment, currentTraceId, }) => { const [expandedKgs, setExpandedKgs] = useState>(new Set()); const { actions } = useAgentGraph(); // Filter to show final/main knowledge graphs with more permissive logic const finalKGs = knowledgeGraphs.filter((kg) => { const hasValidId = Boolean(kg.kg_id || kg.id); if (!hasValidId) { console.warn("Found knowledge graph with missing ID - skipping:", kg); return false; } // Use the exact logic from stage_processor.js to determine if a KG is final const isFinal = kg.is_final === true || (kg.window_index === null && kg.window_total !== null); if (!isFinal) { console.log( "Skipping non-final KG:", kg.kg_id, "window_index:", kg.window_index, "is_final:", kg.is_final ); } else { console.log( "Including final KG:", kg.kg_id, "is_final:", kg.is_final, "window_index:", kg.window_index, "window_total:", kg.window_total ); } return isFinal; }); // Sort by creation timestamp, newest first (matching stage_processor.js) const sortedFinalKGs = finalKGs.sort((a, b) => { const dateA = a.created_at ? new Date(a.created_at) : new Date(0); const dateB = b.created_at ? new Date(b.created_at) : new Date(0); return dateB.getTime() - dateA.getTime(); }); const toggleExpanded = (kgId: string) => { const newExpanded = new Set(expandedKgs); if (newExpanded.has(kgId)) { newExpanded.delete(kgId); } else { newExpanded.add(kgId); } setExpandedKgs(newExpanded); }; const getDisplayName = (kg: KnowledgeGraph) => { // Use system_name if available, otherwise fall back to filename if (kg.system_name) { return kg.system_name; } // Fallback to original filename logic const baseId = kg.kg_id || "unknown"; let displayName = kg.filename || `Knowledge Graph #${baseId}`; // Strip the _knowledge_graph_{timestamp}_{uuid}.json part if present if (displayName.includes("_knowledge_graph_")) { displayName = displayName.split("_knowledge_graph_")[0] || displayName; } return displayName; }; const getSystemSummary = (kg: KnowledgeGraph) => { return kg.system_summary || ""; }; if (sortedFinalKGs.length === 0) { return (

No final knowledge graphs found for this trace.

Generate a knowledge graph first to continue with pipeline processing.

); } return (
{sortedFinalKGs.map((kg) => { const kgId = kg.kg_id || "unknown"; const isSelected = false; // Selection removed const isExpanded = expandedKgs.has(kgId); const windowCount = kg.window_knowledge_graphs ? kg.window_knowledge_graphs.length : kg.window_total || 0; const formattedDate = kg.created_at ? new Date(kg.created_at).toLocaleString() : "Unknown"; return (
{getDisplayName(kg)}
{/* Delete button in top right */}
{/* System summary if available */} {getSystemSummary(kg) && (
{getSystemSummary(kg)}
)}
{formattedDate} {/* Processing metadata */} {(kg as any).processing_metadata && ( <> {(kg as any).processing_metadata.method_name && (kg as any).processing_metadata.method_name !== "unknown" && ( Method:{" "} {(kg as any).processing_metadata.method_name} )} {(kg as any).processing_metadata.splitter_type && (kg as any).processing_metadata.splitter_type !== "unknown" && ( Splitter:{" "} {(kg as any).processing_metadata.splitter_type} )} )} {/* Entity and Relation Statistics - next to splitter info */} {(kg.entity_count !== undefined || kg.relation_count !== undefined) && (
Entities: {kg.entity_count || 0} • Relations:{" "} {kg.relation_count || 0}
)}
{/* Primary actions */}
{/* Secondary actions */} {windowCount > 0 && (
{windowCount > 0 && onReplayKg && currentTraceId && ( )}
)}
{/* Window Knowledge Graphs */} {windowCount > 0 && ( toggleExpanded(kgId)} >

Window Agent Graphs {windowCount}

These window agent graphs are automatically merged to create the final agent graph shown above. Each window represents a portion of the original trace content, allowing you to examine specific sections and their relationships in detail.

{/* Window Agent Graphs Table */}
Window
Character Range
Statistics
Actions
{kg.window_knowledge_graphs ? ( kg.window_knowledge_graphs.map((windowKg) => { const entityCount = windowKg.entity_count || 0; const relationCount = windowKg.relation_count || 0; return (
Window {(windowKg.window_index || 0) + 1}
{windowKg.window_start_char?.toLocaleString() || "N/A"}{" "} -{" "} {windowKg.window_end_char?.toLocaleString() || "N/A"}
Entities: {entityCount} • Relations:{" "} {relationCount}
{onViewSegment && currentTraceId && ( )}
); }) ) : (
No window agent graphs are available for this trace. This may occur if the trace was processed as a single unit without windowing.
)}
)}
); })}
); };