wu981526092's picture
🚀 Deploy AgentGraph: Complete agent monitoring and knowledge graph system
c2ea5ed
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>
);
};