import React, { useState } from "react"; import { Button } from "@/components/ui/button"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Badge } from "@/components/ui/badge"; import { Database, ChevronDown, Clock, Activity, GitBranch, GitCompare, } from "lucide-react"; import { AvailableGraph } from "@/types"; interface GraphSelectorProps { availableGraphs: AvailableGraph[]; selectedGraph1: AvailableGraph | null; selectedGraph2: AvailableGraph | null; onSelectionChange: ( graph1: AvailableGraph | null, graph2: AvailableGraph | null ) => void; onCompareGraphs: () => Promise; isLoading: boolean; } export const GraphSelector: React.FC = ({ availableGraphs, selectedGraph1, selectedGraph2, onSelectionChange, onCompareGraphs, isLoading, }) => { const [expandedGraphs, setExpandedGraphs] = useState>(new Set()); const toggleGraphExpansion = (graphId: number) => { const newExpanded = new Set(expandedGraphs); if (newExpanded.has(graphId)) { newExpanded.delete(graphId); } else { newExpanded.add(graphId); } setExpandedGraphs(newExpanded); }; const handleGraphSelect = (graph: AvailableGraph) => { // If clicking on already selected graph, deselect it if (selectedGraph1?.id === graph.id) { onSelectionChange(null, selectedGraph2); return; } if (selectedGraph2?.id === graph.id) { onSelectionChange(selectedGraph1, null); return; } // Select in first available slot if (!selectedGraph1) { onSelectionChange(graph, selectedGraph2); } else if (!selectedGraph2) { onSelectionChange(selectedGraph1, graph); } else { // Both slots filled, replace the first one onSelectionChange(graph, selectedGraph2); } }; const clearAllSelections = () => { onSelectionChange(null, null); }; const getSelectionClass = (graph: AvailableGraph) => { if (selectedGraph1?.id === graph.id) return "selected selected-1"; if (selectedGraph2?.id === graph.id) return "selected selected-2"; return ""; }; const formatDate = (dateString: string) => { return new Date(dateString).toLocaleDateString(); }; const getGraphTypeIcon = (type: string) => { switch (type) { case "final": return Activity; case "chunk": return Clock; default: return GitBranch; } }; const truncateGraphName = ( name: string, maxLength: number = 35 ): string => { if (!name) return ""; if (name.length <= maxLength) return name; // Smart truncation - keep beginning and end if possible const start = name.substring(0, Math.floor(maxLength * 0.6)); const end = name.substring(name.length - Math.floor(maxLength * 0.3)); return `${start}...${end}`; }; // Prefer a human-friendly system name if available const getGraphDisplayName = (graph: AvailableGraph | null | undefined) => { if (!graph) return ""; return graph.system_name && graph.system_name.trim().length > 0 ? graph.system_name : graph.filename; }; const getSelectionNumber = (graph: AvailableGraph) => { if (selectedGraph1?.id === graph.id) return "1"; if (selectedGraph2?.id === graph.id) return "2"; return null; }; const canCompare = selectedGraph1 && selectedGraph2; return (
{/* Header */}

Select Graphs to Compare

Choose exactly 2 knowledge graphs for comparison

{canCompare && ( )} {(selectedGraph1 || selectedGraph2) && ( )}
{/* Selection Indicators */}
1
{selectedGraph1 ? (
{getGraphDisplayName(selectedGraph1)}
{selectedGraph1.graph_type} •{" "} {selectedGraph1.entity_count} entities
) : (
Select first graph
)}
2
{selectedGraph2 ? (
{getGraphDisplayName(selectedGraph2)}
{selectedGraph2.graph_type} •{" "} {selectedGraph2.entity_count} entities
) : (
Select second graph
)}
{/* Graph List */}
{availableGraphs.length === 0 ? (

No Graphs Available

No knowledge graphs found for comparison. Upload traces and generate graphs first.

) : ( Available Graphs {availableGraphs.length}
{availableGraphs.map((graph) => { const hasChunks = graph.chunk_graphs && graph.chunk_graphs.length > 0; const isExpanded = expandedGraphs.has(graph.id); const IconComponent = getGraphTypeIcon(graph.graph_type); return (
{/* Final Graph Item */}
handleGraphSelect(graph)} >
{getGraphDisplayName(graph)}
{graph.graph_type} {graph.trace_title && ( from{" "} {truncateGraphName(graph.trace_title, 25)} )}
{graph.entity_count}
entities
{graph.relation_count}
relations
{hasChunks && (
{graph.chunk_graphs!.length}
chunks
)}
{formatDate(graph.creation_timestamp)}
created
{hasChunks && ( )}
{/* Selection Number Badge */} {getSelectionNumber(graph) && (
{getSelectionNumber(graph)}
)}
{/* Chunk Graphs */} {hasChunks && isExpanded && (
{graph.chunk_graphs!.map((chunk) => { const ChunkIcon = getGraphTypeIcon( chunk.graph_type ); return (
handleGraphSelect(chunk)} >
Window{" "} {(chunk.window_info?.index || 0) + 1}/ {chunk.window_info?.total || "?"}
{chunk.graph_type} {chunk.window_info && ( {chunk.window_info.start_char?.toLocaleString() || "N/A"}{" "} -{" "} {chunk.window_info.end_char?.toLocaleString() || "N/A"}{" "} chars )}
{chunk.entity_count}
entities
{chunk.relation_count}
relations
{/* Selection Number Badge for Chunks */} {getSelectionNumber(chunk) && (
{getSelectionNumber(chunk)}
)}
); })}
)}
); })}
)}
{/* Selection Summary removed */}
); };