import React, { useEffect, useCallback, useState, useMemo } from "react"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Skeleton } from "@/components/ui/skeleton"; import { Badge } from "@/components/ui/badge"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select"; import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, } from "@/components/ui/tooltip"; import { FileText, GitBranch, Search, Filter, X, ArrowUpDown, Eye, Trash2, Info, HelpCircle, } from "lucide-react"; import { useAgentGraph } from "@/context/AgentGraphContext"; import { useModal } from "@/context/ModalContext"; import { EmptyState } from "@/components/shared/EmptyState"; import { api } from "@/lib/api"; // Types for filtering and sorting type SortField = "name" | "date" | "content" | "type" | "status"; type SortDirection = "asc" | "desc"; type StatusFilter = "all" | "processed" | "ready"; type TypeFilter = "all" | string; export function TracesView() { const { state, actions } = useAgentGraph(); const { openModal } = useModal(); const { traces, isLoading } = state; // Search and filter states const [searchQuery, setSearchQuery] = useState(""); const [typeFilter, setTypeFilter] = useState("all"); const [statusFilter, setStatusFilter] = useState("all"); const [sortField, setSortField] = useState("date"); const [sortDirection, setSortDirection] = useState("desc"); // Simple bulk selection state const [selectedTraces, setSelectedTraces] = useState([]); // Format trace type function const formatTraceType = (type?: string) => { if (!type) return "Unknown"; return type.replace(/[-_]/g, " ").replace(/\b\w/g, (l) => l.toUpperCase()); }; // Format date function const formatDate = (dateString?: string) => { if (!dateString) return "N/A"; try { const date = new Date(dateString); return date.toLocaleDateString(); } catch { return "N/A"; } }; // Get unique trace types for filter dropdown const uniqueTypes = useMemo(() => { const types = new Set(); traces.forEach((trace) => { if (trace.trace_type) { types.add(trace.trace_type); } }); return Array.from(types).sort(); }, [traces]); // Filter and sort traces const filteredAndSortedTraces = useMemo(() => { let filtered = traces; // Apply search filter if (searchQuery.trim()) { const query = searchQuery.toLowerCase(); filtered = filtered.filter( (trace) => trace.filename.toLowerCase().includes(query) || trace.description?.toLowerCase().includes(query) || trace.title?.toLowerCase().includes(query) ); } // Apply type filter if (typeFilter !== "all") { filtered = filtered.filter((trace) => trace.trace_type === typeFilter); } // Apply status filter if (statusFilter !== "all") { filtered = filtered.filter((trace) => { const hasGraphs = trace.knowledge_graphs && trace.knowledge_graphs.length > 0; if (statusFilter === "processed") return hasGraphs; if (statusFilter === "ready") return !hasGraphs; return true; }); } // Apply sorting filtered.sort((a, b) => { let valueA: any, valueB: any; switch (sortField) { case "name": valueA = a.filename.toLowerCase(); valueB = b.filename.toLowerCase(); break; case "date": // Sort by upload timestamp (added date) valueA = new Date(a.upload_timestamp || a.timestamp || 0).getTime(); valueB = new Date(b.upload_timestamp || b.timestamp || 0).getTime(); break; case "content": valueA = a.character_count || 0; valueB = b.character_count || 0; break; case "type": valueA = a.trace_type || ""; valueB = b.trace_type || ""; break; case "status": valueA = (a.knowledge_graphs?.length || 0) > 0 ? 1 : 0; valueB = (b.knowledge_graphs?.length || 0) > 0 ? 1 : 0; break; default: valueA = a.filename; valueB = b.filename; } if (valueA < valueB) return sortDirection === "asc" ? -1 : 1; if (valueA > valueB) return sortDirection === "asc" ? 1 : -1; return 0; }); return filtered; }, [traces, searchQuery, typeFilter, statusFilter, sortField, sortDirection]); // Clear all filters const clearFilters = () => { setSearchQuery(""); setTypeFilter("all"); setStatusFilter("all"); setSortField("date"); setSortDirection("desc"); }; // Check if any filters are active const hasActiveFilters = searchQuery.trim() || typeFilter !== "all" || statusFilter !== "all"; // Simplified bulk operation handlers const handleSelectAll = (checked: boolean) => { if (checked) { setSelectedTraces( filteredAndSortedTraces.map((trace) => trace.id.toString()) ); } else { setSelectedTraces([]); } }; const handleSelectTrace = (traceId: string, checked: boolean) => { if (checked) { setSelectedTraces((prev) => [...prev, traceId]); } else { setSelectedTraces((prev) => prev.filter((id) => id !== traceId)); } }; const handleBulkDelete = () => { if (selectedTraces.length === 0) return; const confirmed = window.confirm( `Are you sure you want to delete ${selectedTraces.length} trace${ selectedTraces.length !== 1 ? "s" : "" }? This action cannot be undone.` ); if (confirmed) { // Simple deletion without complex loading states selectedTraces.forEach(async (traceId) => { try { await api.traces.delete(traceId); } catch (error) { console.error("Error deleting trace:", error); } }); // Refresh traces and clear selection setTimeout(() => { loadTraces(); setSelectedTraces([]); }, 500); } }; const handleViewTrace = (trace: any, e: React.MouseEvent) => { e.stopPropagation(); actions.setSelectedTrace(trace); actions.setActiveView("trace-kg"); }; const handleViewTraceDetails = async (trace: any, e: React.MouseEvent) => { e.stopPropagation(); openModal( "trace-details", `Trace Details - ${trace.filename}`, { trace: trace, knowledgeGraphs: trace.knowledge_graphs || [], }, { size: "xl", closable: true, } ); }; const handleDeleteSingleTrace = async ( traceId: string, e: React.MouseEvent ) => { e.stopPropagation(); if (window.confirm("Are you sure you want to delete this trace?")) { try { await api.traces.delete(traceId); await loadTraces(); } catch (error) { console.error("Error deleting trace:", error); alert("Error deleting trace. Please try again."); } } }; const isAllSelected = filteredAndSortedTraces.length > 0 && filteredAndSortedTraces.every((trace) => selectedTraces.includes(trace.id.toString()) ); const isPartiallySelected = selectedTraces.length > 0 && !isAllSelected; const loadTraces = useCallback(async () => { actions.setLoading(true); try { const tracesData = await api.traces.list(); actions.setTraces(Array.isArray(tracesData) ? tracesData : []); } catch (error) { actions.setError( error instanceof Error ? error.message : "Failed to load traces" ); actions.setTraces([]); } finally { actions.setLoading(false); } }, [actions]); useEffect(() => { loadTraces(); }, [loadTraces]); // Auto-refresh traces every 60 seconds (increased for HF Spaces compatibility) useEffect(() => { const interval = setInterval(() => { if (!isLoading) { console.log("🔄 Auto-refreshing traces..."); loadTraces(); } }, 60000); // 60 seconds (increased from 12s to avoid 429 errors) return () => clearInterval(interval); }, [loadTraces, isLoading]); const handleUploadTrace = () => { actions.setActiveView("upload"); }; return (
{/* Main Content */}
{/* Search and Filter Toolbar */}
{/* Search Bar */}
setSearchQuery(e.target.value)} className="pl-10" />
{/* Filters Row */}
{/* Type Filter */}
{/* Status Filter */} {/* Sort Options */}
{/* Clear Filters */} {hasActiveFilters && ( )} {/* Results Count */}
{filteredAndSortedTraces.length} of {traces.length} traces
{/* Simple Bulk Operations */} {selectedTraces.length > 0 && (
{selectedTraces.length} trace {selectedTraces.length !== 1 ? "s" : ""} selected
)} {/* Content Area */} {isLoading ? (
{[...Array(5)].map((_, i) => (
))}
) : traces.length === 0 ? (

Supported formats: JSON, TXT, LOG

) : (
{filteredAndSortedTraces.map((trace) => ( { actions.setSelectedTrace(trace); actions.setActiveView("trace-kg"); }} onMouseDown={(e) => { // Add pressed effect e.currentTarget.style.transform = "scale(0.995)"; e.currentTarget.style.boxShadow = "inset 0 2px 4px rgba(0,0,0,0.1)"; }} onMouseUp={(e) => { // Remove pressed effect e.currentTarget.style.transform = "scale(1)"; e.currentTarget.style.boxShadow = "none"; }} onMouseLeave={(e) => { // Reset on mouse leave e.currentTarget.style.transform = "scale(1)"; e.currentTarget.style.boxShadow = "none"; }} > ))}
{ if (checkbox) checkbox.indeterminate = isPartiallySelected; }} onChange={(e) => handleSelectAll(e.target.checked)} className="rounded" />
Name

Name and description of the uploaded trace file

Type

Source or format of the trace data

Date

When the trace was uploaded to the system

Content

Character count and content size information

Graphs

Number of generated agent graphs for this trace

Actions
e.stopPropagation()} > handleSelectTrace( trace.id.toString(), e.target.checked ) } className="rounded" />
{trace.filename}
{trace.description && (
{trace.description}
)}
{formatTraceType(trace.trace_type)}
{formatDate( trace.upload_timestamp || trace.timestamp )}
{trace.upload_timestamp || trace.timestamp ? new Date( trace.upload_timestamp || trace.timestamp! ).toLocaleTimeString() : "No time available"}
{trace.character_count ? `${(trace.character_count / 1000).toFixed(0)}K` : "N/A"}
{trace.character_count ? `${trace.character_count.toLocaleString()} chars` : "No content"}
{trace.knowledge_graphs && trace.knowledge_graphs.length > 0 ? ( <> { trace.knowledge_graphs.filter( (kg) => kg.is_final === true || (kg.window_index === null && kg.window_total !== null) ).length } ) : ( None )}
)}
); }