Spaces:
Running
Running
| import React, { useState } from "react"; | |
| import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; | |
| import { Badge } from "@/components/ui/badge"; | |
| import { Button } from "@/components/ui/button"; | |
| import { | |
| Tooltip, | |
| TooltipContent, | |
| TooltipProvider, | |
| TooltipTrigger, | |
| } from "@/components/ui/tooltip"; | |
| import { | |
| Info, | |
| Network, | |
| ArrowRight, | |
| ChevronLeft, | |
| ChevronRight, | |
| AlertTriangle, | |
| Cpu, | |
| Hash, | |
| ExternalLink, | |
| Zap, | |
| } from "lucide-react"; | |
| import { TemporalNode, TemporalLink, GraphFrame } from "@/types/temporal"; | |
| import { | |
| UniversalNode, | |
| UniversalLink, | |
| UniversalGraphData, | |
| FailureItem, | |
| } from "@/types/graph-visualization"; | |
| import { KnowledgeGraph, OptimizationRecommendation } from "@/types"; | |
| import { | |
| MonacoEditorWithHighlights, | |
| Range, | |
| } from "@/components/shared/MonacoEditorWithHighlights"; | |
| import { InteractiveSystemSummary } from "@/components/shared/InteractiveSystemSummary"; | |
| // Entity-type-specific importance definitions from the multi-agent knowledge extractor | |
| const ENTITY_IMPORTANCE_BY_TYPE = { | |
| Agent: { | |
| HIGH: "Core agents that coordinate or manage other agents", | |
| MEDIUM: "Supporting agents with specialized but non-critical functions", | |
| LOW: "Auxiliary agents with very specific or rare functions", | |
| }, | |
| Task: { | |
| HIGH: "Critical tasks that are essential for system function or user goals", | |
| MEDIUM: "Standard operational tasks that support the main workflow", | |
| LOW: "Simple tasks with minimal impact on overall system success", | |
| }, | |
| Tool: { | |
| HIGH: "Essential tools that multiple agents depend on", | |
| MEDIUM: "Commonly used tools that enhance functionality", | |
| LOW: "Rarely used tools or utilities", | |
| }, | |
| Input: { | |
| HIGH: "Primary inputs that drive the entire workflow", | |
| MEDIUM: "Secondary inputs that provide additional context", | |
| LOW: "Optional inputs that provide minor enhancements", | |
| }, | |
| Output: { | |
| HIGH: "Final outputs that represent the main system deliverables", | |
| MEDIUM: "Intermediate outputs that feed into other processes", | |
| LOW: "Diagnostic or logging outputs", | |
| }, | |
| Human: { | |
| HIGH: "Key human stakeholders who make critical decisions", | |
| MEDIUM: "Regular human users who provide routine input", | |
| LOW: "Occasional human observers or reviewers", | |
| }, | |
| }; | |
| // Fallback combined definitions for unknown entity types | |
| const ENTITY_IMPORTANCE_DEFINITIONS = { | |
| HIGH: "Core agents that coordinate or manage other agents • Critical tasks that are essential for system function or user goals • Essential tools that multiple agents depend on • Primary inputs that drive the entire workflow • Final outputs that represent the main system deliverables • Key human stakeholders who make critical decisions", | |
| MEDIUM: | |
| "Supporting agents with specialized but non-critical functions • Standard operational tasks that support the main workflow • Commonly used tools that enhance functionality • Secondary inputs that provide additional context • Intermediate outputs that feed into other processes • Regular human users who provide routine input", | |
| LOW: "Auxiliary agents with very specific or rare functions • Simple tasks with minimal impact on overall system success • Rarely used tools or utilities • Optional inputs that provide minor enhancements • Diagnostic or logging outputs • Occasional human observers or reviewers", | |
| }; | |
| const RELATION_IMPORTANCE_DEFINITIONS = { | |
| HIGH: "Critical data flows that are essential for system operation • Core agent-task assignments that drive main functionality • Essential tool usage that multiple workflows depend on • Primary input consumption that initiates key processes • Final output delivery to key stakeholders • Critical intervention relationships that prevent failures", | |
| MEDIUM: | |
| "Standard operational workflows and data processing • Common agent-task interactions in normal operation • Regular tool usage that supports functionality • Secondary input processing that provides context • Intermediate output generation for downstream processes • Routine human interactions and feedback loops", | |
| LOW: "Auxiliary connections with minimal system impact • Optional workflow steps that can be skipped • Rarely used tool interactions or utilities • Diagnostic or logging data flows • Backup or redundant relationships • Occasional human oversight or monitoring", | |
| }; | |
| interface ElementInfoSidebarProps { | |
| selectedElement: | |
| | TemporalNode | |
| | TemporalLink | |
| | UniversalNode | |
| | UniversalLink | |
| | FailureItem | |
| | null; | |
| selectedElementType: "node" | "link" | "failure" | null; | |
| currentData: GraphFrame | UniversalGraphData; | |
| failures?: { | |
| id: string; | |
| risk_type: string; | |
| description: string; | |
| }[]; | |
| optimizations?: OptimizationRecommendation[]; | |
| onFailureSelect?: (failure: any) => void; | |
| // New props for System and Trace tabs | |
| knowledgeGraph?: KnowledgeGraph; | |
| numberedLines?: string[]; | |
| highlightRanges?: Range[]; | |
| showTraceTab?: boolean; | |
| // Callback for showing trace with highlighting | |
| onShowInTrace?: (ranges: Range[]) => void; | |
| // New callback for entity clicks from summary | |
| onEntitySelect?: (entityId: string) => void; | |
| } | |
| export const ElementInfoSidebar: React.FC<ElementInfoSidebarProps> = ({ | |
| selectedElement, | |
| selectedElementType, | |
| currentData, | |
| failures = [], | |
| optimizations = [], | |
| onFailureSelect, | |
| knowledgeGraph, | |
| numberedLines = [], | |
| highlightRanges = [], | |
| showTraceTab = true, | |
| onShowInTrace, | |
| onEntitySelect, | |
| }) => { | |
| const [isCollapsed, setIsCollapsed] = useState(false); | |
| // Default to system tab when knowledge graph is available, otherwise failures if they exist, otherwise system | |
| const getDefaultTab = () => { | |
| if (knowledgeGraph) { | |
| return "system"; | |
| } | |
| if (!selectedElement && failures.length > 0) { | |
| return "failures"; | |
| } | |
| if (!selectedElement && optimizations.length > 0) { | |
| return "optimizations"; | |
| } | |
| return "system"; | |
| }; | |
| const [activeTab, setActiveTab] = useState< | |
| "failures" | "system" | "trace" | "optimizations" | |
| >(getDefaultTab()); | |
| // Remove automatic tab switching - let user control tabs manually | |
| // useEffect(() => { | |
| // if ( | |
| // selectedElement && | |
| // selectedElementType && | |
| // selectedElementType !== "failure" | |
| // ) { | |
| // // When user selects a node/link (not failure), switch to details tab | |
| // setActiveTab("details"); | |
| // } else if (!selectedElement && failures.length > 0) { | |
| // // When nothing is selected but failures exist, show failures | |
| // setActiveTab("failures"); | |
| // } | |
| // }, [selectedElement, selectedElementType, failures.length]); | |
| console.log("ElementInfoSidebar props:", { | |
| selectedElement, | |
| selectedElementType, | |
| currentData: currentData | |
| ? { nodes: currentData.nodes.length, links: currentData.links.length } | |
| : null, | |
| }); | |
| if (isCollapsed) { | |
| return ( | |
| <div className="w-4 flex-shrink-0 border-l bg-muted/20 flex items-center justify-center transition-all duration-300"> | |
| <button | |
| className="p-1 hover:bg-muted/30 rounded" | |
| onClick={() => setIsCollapsed(false)} | |
| title="Expand Details" | |
| > | |
| <ChevronLeft className="h-4 w-4" /> | |
| </button> | |
| </div> | |
| ); | |
| } | |
| const renderContent = () => { | |
| if (!selectedElement || !selectedElementType) { | |
| return ( | |
| <div className="flex flex-col items-center justify-center h-full text-center"> | |
| <Info className="h-12 w-12 text-muted-foreground mb-4" /> | |
| <h3 className="text-lg font-medium mb-2">No Selection</h3> | |
| <p className="text-sm text-muted-foreground"> | |
| Click on a node or relation to view its details | |
| </p> | |
| </div> | |
| ); | |
| } | |
| if (selectedElementType === "node") { | |
| return renderNodeInfo(selectedElement as TemporalNode | UniversalNode); | |
| } else if (selectedElementType === "link") { | |
| return renderLinkInfo(selectedElement as TemporalLink | UniversalLink); | |
| } else { | |
| // For failures, don't show details here - let failures tab handle it | |
| return ( | |
| <div className="flex flex-col items-center justify-center h-full text-center"> | |
| <Info className="h-12 w-12 text-muted-foreground mb-4" /> | |
| <h3 className="text-lg font-medium mb-2">Failure Selected</h3> | |
| <p className="text-sm text-muted-foreground"> | |
| View failure details in the Failures tab | |
| </p> | |
| </div> | |
| ); | |
| } | |
| }; | |
| const renderNodeInfo = (node: TemporalNode | UniversalNode) => { | |
| // Find connections for this node | |
| const connections = currentData.links.filter( | |
| (link) => | |
| (typeof link.source === "object" ? link.source.id : link.source) === | |
| node.id || | |
| (typeof link.target === "object" ? link.target.id : link.target) === | |
| node.id | |
| ); | |
| // Get importance from node data | |
| const importance = (node as any).importance; | |
| return ( | |
| <Card> | |
| <CardHeader className="pb-3"> | |
| <CardTitle className="flex items-center gap-2 text-base"> | |
| <Network className="h-4 w-4" /> | |
| Node Details | |
| </CardTitle> | |
| </CardHeader> | |
| <CardContent className="space-y-3"> | |
| {/* Main Info Section - Improved layout to prevent overflow */} | |
| <div className="space-y-2"> | |
| <div className="flex items-start justify-between gap-2"> | |
| <div className="flex-1 min-w-0"> | |
| <h4 | |
| className="font-medium text-base mb-1 truncate" | |
| title={node.name || node.id} | |
| > | |
| {node.name || node.id} | |
| </h4> | |
| <div className="flex items-center gap-2 flex-wrap"> | |
| <Badge variant="secondary" className="text-xs"> | |
| {node.type || "Unknown"} | |
| </Badge> | |
| {/* Moved importance badge here */} | |
| {importance && ( | |
| <TooltipProvider delayDuration={300}> | |
| <Tooltip> | |
| <TooltipTrigger asChild> | |
| <div className="cursor-help"> | |
| <Badge | |
| variant={ | |
| importance === "HIGH" | |
| ? "destructive" | |
| : importance === "MEDIUM" | |
| ? "default" | |
| : "secondary" | |
| } | |
| className="text-xs font-medium" | |
| > | |
| {importance} | |
| </Badge> | |
| </div> | |
| </TooltipTrigger> | |
| <TooltipContent> | |
| <p className="font-medium">{importance} Importance</p> | |
| <p className="text-xs"> | |
| {ENTITY_IMPORTANCE_BY_TYPE[ | |
| node.type as keyof typeof ENTITY_IMPORTANCE_BY_TYPE | |
| ]?.[ | |
| importance as keyof typeof ENTITY_IMPORTANCE_BY_TYPE.Agent | |
| ] || | |
| ENTITY_IMPORTANCE_DEFINITIONS[ | |
| importance as keyof typeof ENTITY_IMPORTANCE_DEFINITIONS | |
| ] | |
| ?.split("•")[0] | |
| ?.trim() || | |
| "No definition available"} | |
| </p> | |
| </TooltipContent> | |
| </Tooltip> | |
| </TooltipProvider> | |
| )} | |
| </div> | |
| </div> | |
| {/* Show in Trace button for nodes - Icon only to save space */} | |
| {showTraceTab && | |
| ((node as any).raw_text_ref || (node as any).raw_prompt_ref) && | |
| Array.isArray( | |
| (node as any).raw_text_ref || (node as any).raw_prompt_ref | |
| ) && | |
| ((node as any).raw_text_ref || (node as any).raw_prompt_ref) | |
| .length > 0 && ( | |
| <TooltipProvider delayDuration={300}> | |
| <Tooltip> | |
| <TooltipTrigger asChild> | |
| <Button | |
| variant="ghost" | |
| size="sm" | |
| className="h-6 w-6 p-0 flex-shrink-0" | |
| onClick={(e) => { | |
| e.stopPropagation(); | |
| // Prefer raw_text_ref over raw_prompt_ref | |
| const traceRefs = ((node as any).raw_text_ref || | |
| (node as any).raw_prompt_ref) as Array<{ | |
| line_start: number; | |
| line_end: number; | |
| }>; | |
| const ranges: Range[] = traceRefs.map((ref) => ({ | |
| start: ref.line_start, | |
| end: ref.line_end, | |
| })); | |
| // Switch to trace tab and highlight ranges | |
| setActiveTab("trace"); | |
| onShowInTrace?.(ranges); | |
| }} | |
| > | |
| <ExternalLink className="h-3 w-3" /> | |
| </Button> | |
| </TooltipTrigger> | |
| <TooltipContent> | |
| <p>Show in Trace</p> | |
| </TooltipContent> | |
| </Tooltip> | |
| </TooltipProvider> | |
| )} | |
| </div> | |
| </div> | |
| {/* Connections Section - Simplified */} | |
| {connections.length > 0 && ( | |
| <div> | |
| <p className="font-medium mb-2 text-sm text-muted-foreground"> | |
| Connections ({connections.length}) | |
| </p> | |
| <div className="space-y-1 max-h-32 overflow-y-auto"> | |
| {connections.map((link, index) => { | |
| const isSource = | |
| (typeof link.source === "object" | |
| ? link.source.id | |
| : link.source) === node.id; | |
| const connectedNodeId = isSource | |
| ? typeof link.target === "object" | |
| ? link.target.id | |
| : link.target | |
| : typeof link.source === "object" | |
| ? link.source.id | |
| : link.source; | |
| const connectedNode = currentData.nodes.find( | |
| (n) => n.id === connectedNodeId | |
| ); | |
| return ( | |
| <div | |
| key={index} | |
| className="flex items-center justify-between text-xs p-2 bg-muted/20 rounded border" | |
| > | |
| <div className="flex items-center gap-1.5 flex-1 min-w-0"> | |
| <ArrowRight | |
| className={`h-3 w-3 text-muted-foreground flex-shrink-0 ${ | |
| isSource ? "" : "rotate-180" | |
| }`} | |
| /> | |
| <span className="truncate text-xs"> | |
| {connectedNode?.name || connectedNodeId} | |
| </span> | |
| </div> | |
| <Badge | |
| variant="outline" | |
| className="text-xs px-1 py-0 ml-1" | |
| > | |
| {link.type || "related"} | |
| </Badge> | |
| </div> | |
| ); | |
| })} | |
| </div> | |
| </div> | |
| )} | |
| </CardContent> | |
| </Card> | |
| ); | |
| }; | |
| const renderLinkInfo = (link: TemporalLink | UniversalLink) => { | |
| const sourceId = | |
| typeof link.source === "object" ? link.source.id : link.source; | |
| const targetId = | |
| typeof link.target === "object" ? link.target.id : link.target; | |
| const sourceNode = currentData.nodes.find((n) => n.id === sourceId); | |
| const targetNode = currentData.nodes.find((n) => n.id === targetId); | |
| // Get importance from link data | |
| const importance = (link as any).importance; | |
| return ( | |
| <Card> | |
| <CardHeader className="pb-3"> | |
| <CardTitle className="flex items-center gap-2 text-base"> | |
| <ArrowRight className="h-4 w-4" /> | |
| Relation Details | |
| </CardTitle> | |
| </CardHeader> | |
| <CardContent className="space-y-3"> | |
| {/* Main Info Section - Improved layout to match Node Details */} | |
| <div className="space-y-2"> | |
| <div className="flex items-start justify-between gap-2"> | |
| <div className="flex-1 min-w-0"> | |
| <h4 | |
| className="font-medium text-base mb-1 truncate" | |
| title={link.type || "Relation"} | |
| > | |
| {link.type || "Relation"} | |
| </h4> | |
| <div className="flex items-center gap-2 flex-wrap"> | |
| <Badge variant="secondary" className="text-xs"> | |
| {link.type || "Unknown"} | |
| </Badge> | |
| {/* Importance badge inline - matching Node Details */} | |
| {importance && ( | |
| <TooltipProvider delayDuration={300}> | |
| <Tooltip> | |
| <TooltipTrigger asChild> | |
| <div className="cursor-help"> | |
| <Badge | |
| variant={ | |
| importance === "HIGH" | |
| ? "destructive" | |
| : importance === "MEDIUM" | |
| ? "default" | |
| : "secondary" | |
| } | |
| className="text-xs font-medium" | |
| > | |
| {importance} | |
| </Badge> | |
| </div> | |
| </TooltipTrigger> | |
| <TooltipContent> | |
| <p className="font-medium">{importance} Importance</p> | |
| <p className="text-xs"> | |
| {RELATION_IMPORTANCE_DEFINITIONS[ | |
| importance as keyof typeof RELATION_IMPORTANCE_DEFINITIONS | |
| ] | |
| ?.split("•")[0] | |
| ?.trim() || "No definition available"} | |
| </p> | |
| </TooltipContent> | |
| </Tooltip> | |
| </TooltipProvider> | |
| )} | |
| </div> | |
| </div> | |
| {/* Show in Trace button for relations - Icon only to save space */} | |
| {showTraceTab && | |
| ((link as any).raw_text_ref || | |
| (link as any).raw_prompt_ref || | |
| (link as any).interaction_prompt_ref) && | |
| Array.isArray( | |
| (link as any).raw_text_ref || | |
| (link as any).raw_prompt_ref || | |
| (link as any).interaction_prompt_ref | |
| ) && | |
| ( | |
| (link as any).raw_text_ref || | |
| (link as any).raw_prompt_ref || | |
| (link as any).interaction_prompt_ref | |
| ).length > 0 && ( | |
| <TooltipProvider delayDuration={300}> | |
| <Tooltip> | |
| <TooltipTrigger asChild> | |
| <Button | |
| variant="ghost" | |
| size="sm" | |
| className="h-6 w-6 p-0 flex-shrink-0" | |
| onClick={(e) => { | |
| e.stopPropagation(); | |
| // Prefer raw_text_ref, then raw_prompt_ref, then interaction_prompt_ref | |
| const traceRefs = ((link as any).raw_text_ref || | |
| (link as any).raw_prompt_ref || | |
| (link as any).interaction_prompt_ref) as Array<{ | |
| line_start: number; | |
| line_end: number; | |
| }>; | |
| const ranges: Range[] = traceRefs.map((ref) => ({ | |
| start: ref.line_start, | |
| end: ref.line_end, | |
| })); | |
| // Switch to trace tab and highlight ranges | |
| setActiveTab("trace"); | |
| onShowInTrace?.(ranges); | |
| }} | |
| > | |
| <ExternalLink className="h-3 w-3" /> | |
| </Button> | |
| </TooltipTrigger> | |
| <TooltipContent> | |
| <p>Show in Trace</p> | |
| </TooltipContent> | |
| </Tooltip> | |
| </TooltipProvider> | |
| )} | |
| </div> | |
| </div> | |
| {/* Connection Section - Simplified to match Node Details style */} | |
| <div> | |
| <p className="font-medium mb-2 text-sm text-muted-foreground"> | |
| Connection | |
| </p> | |
| <div className="space-y-1 max-h-32 overflow-y-auto"> | |
| <div className="flex items-center justify-between text-xs p-2 bg-muted/20 rounded border"> | |
| <div className="flex items-center gap-1.5 flex-1 min-w-0"> | |
| <span className="text-xs font-medium text-muted-foreground min-w-[45px]"> | |
| Source: | |
| </span> | |
| <span className="truncate text-xs"> | |
| {sourceNode?.name || sourceId} | |
| </span> | |
| </div> | |
| </div> | |
| <div className="flex items-center justify-between text-xs p-2 bg-muted/20 rounded border"> | |
| <div className="flex items-center gap-1.5 flex-1 min-w-0"> | |
| <ArrowRight className="h-3 w-3 text-muted-foreground flex-shrink-0" /> | |
| <span className="text-xs font-medium text-muted-foreground min-w-[45px]"> | |
| Target: | |
| </span> | |
| <span className="truncate text-xs"> | |
| {targetNode?.name || targetId} | |
| </span> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </CardContent> | |
| </Card> | |
| ); | |
| }; | |
| const renderFailuresContent = () => { | |
| if (failures.length === 0) { | |
| return ( | |
| <div className="flex flex-col items-center justify-center h-full text-center"> | |
| <AlertTriangle className="h-12 w-12 text-muted-foreground mb-4" /> | |
| <h3 className="text-lg font-medium mb-2">No Failures</h3> | |
| <p className="text-sm text-muted-foreground"> | |
| No failures detected in this trace | |
| </p> | |
| </div> | |
| ); | |
| } | |
| return ( | |
| <div className="space-y-3"> | |
| <p className="text-sm text-muted-foreground"> | |
| Failures detected in trace: | |
| </p> | |
| {failures.map((failure) => { | |
| // Use enriched failure data if available, otherwise fallback to basic lookup | |
| const affectedElementName = | |
| (failure as any).displayName || | |
| ((failure as any).affected_id | |
| ? (failure as any).affected_id | |
| : null); | |
| const hasMatchedElement = !!(failure as any).matchedElement; | |
| return ( | |
| <Card | |
| key={failure.id} | |
| className="cursor-pointer hover:bg-muted/50 transition-colors" | |
| onClick={() => onFailureSelect?.(failure)} | |
| > | |
| <CardContent className="p-3 space-y-3"> | |
| {/* Header with risk type and Show in Trace button */} | |
| <div className="flex items-center justify-between gap-2 min-w-0"> | |
| <div className="flex items-center gap-2 min-w-0 flex-1"> | |
| {failure.risk_type === "EXECUTION_ERROR" && ( | |
| <> | |
| <svg | |
| className="h-5 w-5 text-red-500" | |
| fill="none" | |
| stroke="currentColor" | |
| viewBox="0 0 24 24" | |
| > | |
| <path | |
| strokeLinecap="round" | |
| strokeLinejoin="round" | |
| strokeWidth={2} | |
| d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-2.5L13.732 4c-.77-.833-1.732-.833-2.464 0L4.35 16.5c-.77.833.192 2.5 1.732 2.5z" | |
| /> | |
| </svg> | |
| <span className="text-red-500 font-medium text-sm"> | |
| EXECUTION | |
| </span> | |
| </> | |
| )} | |
| {failure.risk_type === "AGENT_ERROR" && ( | |
| <> | |
| <svg | |
| className="h-5 w-5 text-orange-500" | |
| fill="none" | |
| stroke="currentColor" | |
| viewBox="0 0 24 24" | |
| > | |
| <path | |
| strokeLinecap="round" | |
| strokeLinejoin="round" | |
| strokeWidth={2} | |
| d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z" | |
| /> | |
| <path | |
| strokeLinecap="round" | |
| strokeLinejoin="round" | |
| strokeWidth={2} | |
| d="M15 11l-3-3m0 0l-3 3m3-3v12" | |
| /> | |
| </svg> | |
| <span className="text-orange-500 font-medium text-sm"> | |
| AGENT | |
| </span> | |
| </> | |
| )} | |
| {failure.risk_type === "PLANNING_ERROR" && ( | |
| <> | |
| <svg | |
| className="h-5 w-5 text-yellow-600" | |
| fill="none" | |
| stroke="currentColor" | |
| viewBox="0 0 24 24" | |
| > | |
| <path | |
| strokeLinecap="round" | |
| strokeLinejoin="round" | |
| strokeWidth={2} | |
| d="M9 5H7a2 2 0 00-2 2v10a2 2 0 002 2h8a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-3 7h3m-3 4h3m-6-4h.01M9 16h.01" | |
| /> | |
| </svg> | |
| <span className="text-yellow-600 font-medium text-sm"> | |
| PLANNING | |
| </span> | |
| </> | |
| )} | |
| {failure.risk_type === "RETRIEVAL_ERROR" && ( | |
| <> | |
| <svg | |
| className="h-5 w-5 text-blue-500" | |
| fill="none" | |
| stroke="currentColor" | |
| viewBox="0 0 24 24" | |
| > | |
| <path | |
| strokeLinecap="round" | |
| strokeLinejoin="round" | |
| strokeWidth={2} | |
| d="M7 16a4 4 0 01-.88-7.903A5 5 0 1115.9 6L16 6a5 5 0 011 9.9M9 19l3 3m0 0l3-3m-3 3V10" | |
| /> | |
| </svg> | |
| <span className="text-blue-500 font-medium text-sm"> | |
| RETRIEVAL | |
| </span> | |
| </> | |
| )} | |
| {failure.risk_type === "HALLUCINATION" && ( | |
| <> | |
| <svg | |
| className="h-5 w-5 text-purple-600" | |
| fill="none" | |
| stroke="currentColor" | |
| viewBox="0 0 24 24" | |
| > | |
| <path | |
| strokeLinecap="round" | |
| strokeLinejoin="round" | |
| strokeWidth={2} | |
| d="M8.228 9c.549-1.165 2.03-2 3.772-2 2.21 0 4 1.343 4 3 0 1.4-1.278 2.575-3.006 2.907-.542.104-.994.54-.994 1.093m0 3h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" | |
| /> | |
| </svg> | |
| <span className="text-purple-600 font-medium text-sm"> | |
| HALLUCINATION | |
| </span> | |
| </> | |
| )} | |
| {![ | |
| "EXECUTION_ERROR", | |
| "AGENT_ERROR", | |
| "PLANNING_ERROR", | |
| "RETRIEVAL_ERROR", | |
| "HALLUCINATION", | |
| ].includes(failure.risk_type) && ( | |
| <> | |
| <svg | |
| className="h-5 w-5 text-gray-500" | |
| fill="none" | |
| stroke="currentColor" | |
| viewBox="0 0 24 24" | |
| > | |
| <path | |
| strokeLinecap="round" | |
| strokeLinejoin="round" | |
| strokeWidth={2} | |
| d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" | |
| /> | |
| </svg> | |
| <span className="text-gray-500 font-medium text-sm"> | |
| OTHER | |
| </span> | |
| </> | |
| )} | |
| </div> | |
| {/* Show in Trace button - Only show if trace references exist */} | |
| {showTraceTab && | |
| (failure as any).raw_text_ref && | |
| Array.isArray((failure as any).raw_text_ref) && | |
| (failure as any).raw_text_ref.length > 0 && ( | |
| <Button | |
| variant="ghost" | |
| size="sm" | |
| className="text-xs h-6 px-1.5 py-1 flex-shrink-0 whitespace-nowrap" | |
| onClick={(e) => { | |
| e.stopPropagation(); // Prevent card click | |
| const traceRefs = (failure as any) | |
| .raw_text_ref as Array<{ | |
| line_start: number; | |
| line_end: number; | |
| }>; | |
| const ranges: Range[] = traceRefs.map((ref) => ({ | |
| start: ref.line_start, | |
| end: ref.line_end, | |
| })); | |
| // Switch to trace tab and highlight ranges | |
| setActiveTab("trace"); | |
| onShowInTrace?.(ranges); | |
| }} | |
| > | |
| <ExternalLink className="h-3 w-3 mr-1" /> | |
| Trace | |
| </Button> | |
| )} | |
| </div> | |
| {/* Description */} | |
| <div className="rounded-md bg-muted/30 p-2"> | |
| <p className="text-sm text-foreground leading-relaxed"> | |
| {failure.description} | |
| </p> | |
| </div> | |
| {/* Affected Element - Always show if affected_id exists */} | |
| {(failure as any).affected_id && ( | |
| <div className="flex items-center gap-2 text-xs"> | |
| <span className="text-muted-foreground">Affects:</span> | |
| <Badge variant="secondary" className="text-xs"> | |
| {affectedElementName || (failure as any).affected_id} | |
| </Badge> | |
| {!hasMatchedElement && ( | |
| <span className="text-xs text-muted-foreground opacity-70"> | |
| (element not found in graph) | |
| </span> | |
| )} | |
| </div> | |
| )} | |
| </CardContent> | |
| </Card> | |
| ); | |
| })} | |
| </div> | |
| ); | |
| }; | |
| const renderOptimizationsContent = () => { | |
| console.log( | |
| "🔧 renderOptimizationsContent called, optimizations:", | |
| optimizations | |
| ); | |
| return ( | |
| <div className="space-y-3"> | |
| <p className="text-sm text-muted-foreground"> | |
| Recommendations for improving this system: | |
| </p> | |
| {optimizations.map((opt) => { | |
| const knownTypes = [ | |
| "WORKFLOW_SIMPLIFICATION", | |
| "AGENT_MERGING", | |
| "TASK_CONSOLIDATION", | |
| "TOOL_ENHANCEMENT", | |
| "PROMPT_REFINEMENT", | |
| ] as const; | |
| return ( | |
| <Card key={opt.id} className="bg-background"> | |
| <CardContent className="p-3 space-y-3"> | |
| <div className="flex items-center justify-between gap-2 min-w-0"> | |
| <div className="flex items-center gap-2 min-w-0 flex-1"> | |
| {opt.recommendation_type === "WORKFLOW_SIMPLIFICATION" && ( | |
| <> | |
| <svg | |
| className="h-5 w-5 text-red-500" | |
| fill="none" | |
| stroke="currentColor" | |
| viewBox="0 0 24 24" | |
| > | |
| <path | |
| strokeLinecap="round" | |
| strokeLinejoin="round" | |
| strokeWidth={2} | |
| d="M8 16l2.879-2.879m0 0a3 3 0 104.243-4.242 3 3 0 00-4.243 4.242zM21 12a9 9 0 11-18 0 9 9 0 0118 0z" | |
| /> | |
| <path | |
| strokeLinecap="round" | |
| strokeLinejoin="round" | |
| strokeWidth={2} | |
| d="M15 8l-6 6" | |
| /> | |
| </svg> | |
| <span className="text-red-500 font-medium text-sm"> | |
| WORKFLOW | |
| </span> | |
| </> | |
| )} | |
| {opt.recommendation_type === "AGENT_MERGING" && ( | |
| <> | |
| <svg | |
| className="h-5 w-5 text-blue-500" | |
| fill="none" | |
| stroke="currentColor" | |
| viewBox="0 0 24 24" | |
| > | |
| <path | |
| strokeLinecap="round" | |
| strokeLinejoin="round" | |
| strokeWidth={2} | |
| d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7 10a2 2 0 11-4 0 2 2 0 014 0z" | |
| /> | |
| </svg> | |
| <span className="text-blue-500 font-medium text-sm"> | |
| AGENTS | |
| </span> | |
| </> | |
| )} | |
| {opt.recommendation_type === "TASK_CONSOLIDATION" && ( | |
| <> | |
| <svg | |
| className="h-5 w-5 text-green-500" | |
| fill="none" | |
| stroke="currentColor" | |
| viewBox="0 0 24 24" | |
| > | |
| <path | |
| strokeLinecap="round" | |
| strokeLinejoin="round" | |
| strokeWidth={2} | |
| d="M9 5H7a2 2 0 00-2 2v10a2 2 0 002 2h8a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4" | |
| /> | |
| </svg> | |
| <span className="text-green-500 font-medium text-sm"> | |
| TASKS | |
| </span> | |
| </> | |
| )} | |
| {opt.recommendation_type === "TOOL_ENHANCEMENT" && ( | |
| <> | |
| <svg | |
| className="h-5 w-5 text-purple-500" | |
| fill="none" | |
| stroke="currentColor" | |
| viewBox="0 0 24 24" | |
| > | |
| <path | |
| strokeLinecap="round" | |
| strokeLinejoin="round" | |
| strokeWidth={2} | |
| d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z" | |
| /> | |
| <path | |
| strokeLinecap="round" | |
| strokeLinejoin="round" | |
| strokeWidth={2} | |
| d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" | |
| /> | |
| </svg> | |
| <span className="text-purple-500 font-medium text-sm"> | |
| TOOLS | |
| </span> | |
| </> | |
| )} | |
| {opt.recommendation_type === "PROMPT_REFINEMENT" && ( | |
| <> | |
| <svg | |
| className="h-5 w-5 text-pink-500" | |
| fill="none" | |
| stroke="currentColor" | |
| viewBox="0 0 24 24" | |
| > | |
| <path | |
| strokeLinecap="round" | |
| strokeLinejoin="round" | |
| strokeWidth={2} | |
| d="M13 10V3L4 14h7v7l9-11h-7z" | |
| /> | |
| </svg> | |
| <span className="text-pink-500 font-medium text-sm"> | |
| PROMPTS | |
| </span> | |
| </> | |
| )} | |
| {!knownTypes.includes(opt.recommendation_type as any) && ( | |
| <> | |
| <svg | |
| className="h-5 w-5 text-orange-500" | |
| fill="none" | |
| stroke="currentColor" | |
| viewBox="0 0 24 24" | |
| > | |
| <path | |
| strokeLinecap="round" | |
| strokeLinejoin="round" | |
| strokeWidth={2} | |
| d="M9.663 17h4.673M12 3v1m6.364 1.636l-.707.707M21 12h-1M4 12H3m3.343-5.657l-.707-.707m2.828 9.9a5 5 0 117.072 0l-.548.547A3.374 3.374 0 0014 18.469V19a2 2 0 11-4 0v-.531c0-.895-.356-1.754-.988-2.386l-.548-.547z" | |
| /> | |
| </svg> | |
| <span className="text-orange-500 font-medium text-sm"> | |
| OTHER | |
| </span> | |
| </> | |
| )} | |
| </div> | |
| {showTraceTab && | |
| opt.raw_text_ref && | |
| Array.isArray(opt.raw_text_ref) && | |
| opt.raw_text_ref.length > 0 && ( | |
| <Button | |
| variant="ghost" | |
| size="sm" | |
| className="text-xs h-6 px-1.5 py-1 flex-shrink-0 whitespace-nowrap" | |
| onClick={(e) => { | |
| e.stopPropagation(); | |
| const ranges: Range[] = opt.raw_text_ref!.map( | |
| (ref) => ({ | |
| start: ref.line_start, | |
| end: ref.line_end, | |
| }) | |
| ); | |
| setActiveTab("trace"); | |
| onShowInTrace?.(ranges); | |
| }} | |
| > | |
| <ExternalLink className="h-3 w-3 mr-1" /> | |
| Trace | |
| </Button> | |
| )} | |
| </div> | |
| <div className="rounded-md bg-muted/30 p-2"> | |
| <p className="text-sm text-foreground leading-relaxed"> | |
| {opt.description} | |
| </p> | |
| </div> | |
| {(opt as any).affected_nodes && | |
| (opt as any).affected_nodes.length > 0 && ( | |
| <div className="flex items-center gap-2 text-xs flex-wrap"> | |
| <span className="text-muted-foreground">Affects:</span> | |
| {(opt as any).affected_nodes.map( | |
| (node: { id: string; name: string }) => ( | |
| <TooltipProvider key={node.id} delayDuration={300}> | |
| <Tooltip> | |
| <TooltipTrigger> | |
| <Badge | |
| variant="secondary" | |
| className="text-xs cursor-pointer max-w-[150px] truncate" | |
| onClick={() => onEntitySelect?.(node.id)} | |
| > | |
| {node.name} | |
| </Badge> | |
| </TooltipTrigger> | |
| <TooltipContent> | |
| <p>{node.name}</p> | |
| </TooltipContent> | |
| </Tooltip> | |
| </TooltipProvider> | |
| ) | |
| )} | |
| </div> | |
| )} | |
| </CardContent> | |
| </Card> | |
| ); | |
| })} | |
| </div> | |
| ); | |
| }; | |
| // Add System content renderer (based on TraceViewerSidebar) | |
| const renderSystemContent = () => { | |
| if (!knowledgeGraph) { | |
| return ( | |
| <div className="flex items-center justify-center h-full text-center"> | |
| <div> | |
| <Info className="h-12 w-12 text-muted-foreground mx-auto mb-4" /> | |
| <h3 className="text-lg font-medium mb-2">System Information</h3> | |
| <p className="text-sm text-muted-foreground"> | |
| No system data available for this view | |
| </p> | |
| </div> | |
| </div> | |
| ); | |
| } | |
| return ( | |
| <div className="flex flex-col h-full space-y-3 overflow-y-auto"> | |
| {/* System Hero Section */} | |
| <div className="relative overflow-hidden rounded-lg bg-gradient-to-br from-blue-50 via-indigo-50 to-purple-50 dark:from-blue-950/20 dark:via-indigo-950/20 dark:to-purple-950/20 p-4 border border-blue-200/50 dark:border-blue-800/50"> | |
| <div className="relative z-10"> | |
| {knowledgeGraph.system_name && ( | |
| <div className="mb-3"> | |
| <h2 className="text-base font-semibold text-gray-900 dark:text-gray-100 leading-tight"> | |
| {knowledgeGraph.system_name} | |
| </h2> | |
| </div> | |
| )} | |
| {knowledgeGraph.system_summary && ( | |
| <InteractiveSystemSummary | |
| summary={knowledgeGraph.system_summary} | |
| onEntityClick={(entityId) => onEntitySelect?.(entityId)} | |
| selectedNodeId={ | |
| selectedElement && selectedElementType === "node" | |
| ? selectedElement.id | |
| : null | |
| } | |
| /> | |
| )} | |
| </div> | |
| {/* Subtle background pattern */} | |
| <div className="absolute top-0 right-0 opacity-5"> | |
| <Cpu className="h-16 w-16" /> | |
| </div> | |
| </div> | |
| {/* Details Section - Merged from Details tab */} | |
| <div className="border-t pt-3 mt-3">{renderContent()}</div> | |
| </div> | |
| ); | |
| }; | |
| // Add Trace content renderer using the new MonacoEditorWithHighlights component | |
| const renderTraceContent = () => { | |
| if (!numberedLines || numberedLines.length === 0) { | |
| return ( | |
| <div className="flex items-center justify-center h-full text-center"> | |
| <div> | |
| <Hash className="h-12 w-12 text-muted-foreground mx-auto mb-4" /> | |
| <h3 className="text-lg font-medium mb-2">No Trace Data</h3> | |
| <p className="text-sm text-muted-foreground"> | |
| No trace content available for this view | |
| </p> | |
| </div> | |
| </div> | |
| ); | |
| } | |
| return ( | |
| <MonacoEditorWithHighlights | |
| numberedLines={numberedLines} | |
| highlightRanges={highlightRanges} | |
| showNavigationControls={true} | |
| height="100%" | |
| theme="vs-dark" | |
| /> | |
| ); | |
| }; | |
| return ( | |
| <div className="w-80 flex-shrink-0 border-l bg-muted/20 flex flex-col transition-all duration-300"> | |
| <div className="flex items-center justify-between h-10 px-2 border-b text-xs font-semibold text-muted-foreground shrink-0"> | |
| <div className="flex items-center justify-around flex-1"> | |
| <TooltipProvider> | |
| {knowledgeGraph && ( | |
| <Tooltip> | |
| <TooltipTrigger asChild> | |
| <button | |
| className={`p-2 rounded transition-colors ${ | |
| activeTab === "system" | |
| ? "bg-primary/10 text-primary" | |
| : "hover:bg-muted/30" | |
| }`} | |
| onClick={() => setActiveTab("system")} | |
| > | |
| <Cpu className="h-4 w-4" /> | |
| </button> | |
| </TooltipTrigger> | |
| <TooltipContent>System</TooltipContent> | |
| </Tooltip> | |
| )} | |
| <Tooltip> | |
| <TooltipTrigger asChild> | |
| <button | |
| className={`p-2 rounded transition-colors relative ${ | |
| activeTab === "failures" | |
| ? "bg-primary/10 text-primary" | |
| : "hover:bg-muted/30" | |
| }`} | |
| onClick={() => setActiveTab("failures")} | |
| > | |
| <AlertTriangle className="h-4 w-4" /> | |
| {failures.length > 0 && ( | |
| <Badge | |
| variant="destructive" | |
| className="absolute -top-1 -right-1 h-4 w-4 p-0 flex items-center justify-center text-xs" | |
| > | |
| {failures.length} | |
| </Badge> | |
| )} | |
| </button> | |
| </TooltipTrigger> | |
| <TooltipContent>Failures</TooltipContent> | |
| </Tooltip> | |
| {optimizations.length > 0 && ( | |
| <Tooltip> | |
| <TooltipTrigger asChild> | |
| <button | |
| className={`p-2 rounded transition-colors relative ${ | |
| activeTab === "optimizations" | |
| ? "bg-primary/10 text-primary" | |
| : "hover:bg-muted/30" | |
| }`} | |
| onClick={() => setActiveTab("optimizations")} | |
| > | |
| <Zap className="h-4 w-4" /> | |
| <Badge | |
| variant="default" | |
| className="absolute -top-1 -right-1 h-4 w-4 p-0 flex items-center justify-center text-xs" | |
| > | |
| {optimizations.length} | |
| </Badge> | |
| </button> | |
| </TooltipTrigger> | |
| <TooltipContent>Optimization</TooltipContent> | |
| </Tooltip> | |
| )} | |
| {showTraceTab && numberedLines.length > 0 && ( | |
| <Tooltip> | |
| <TooltipTrigger asChild> | |
| <button | |
| className={`p-2 rounded transition-colors ${ | |
| activeTab === "trace" | |
| ? "bg-primary/10 text-primary" | |
| : "hover:bg-muted/30" | |
| }`} | |
| onClick={() => setActiveTab("trace")} | |
| > | |
| <Hash className="h-4 w-4" /> | |
| </button> | |
| </TooltipTrigger> | |
| <TooltipContent>Trace</TooltipContent> | |
| </Tooltip> | |
| )} | |
| </TooltipProvider> | |
| </div> | |
| <button | |
| className="p-1 hover:bg-muted/30 rounded" | |
| onClick={() => setIsCollapsed(true)} | |
| title="Collapse Details" | |
| > | |
| <ChevronRight className="h-4 w-4" /> | |
| </button> | |
| </div> | |
| {/* Content */} | |
| <div className="flex-1 overflow-y-auto p-4"> | |
| {activeTab === "failures" | |
| ? renderFailuresContent() | |
| : activeTab === "system" | |
| ? renderSystemContent() | |
| : activeTab === "trace" | |
| ? renderTraceContent() | |
| : activeTab === "optimizations" | |
| ? renderOptimizationsContent() | |
| : renderSystemContent()} | |
| </div> | |
| {/* Footer with navigation hint */} | |
| {activeTab === "trace" && highlightRanges.length > 0 && ( | |
| <div className="px-2 py-1 border-t text-xs text-muted-foreground/80 shrink-0"> | |
| <div className="flex items-center justify-center gap-1"> | |
| <span>Press</span> | |
| <kbd className="px-1 py-0.5 bg-muted/50 rounded text-xs">{"<"}</kbd> | |
| <kbd className="px-1 py-0.5 bg-muted/50 rounded text-xs">{">"}</kbd> | |
| <span>to navigate</span> | |
| </div> | |
| </div> | |
| )} | |
| </div> | |
| ); | |
| }; | |