import { useCallback, useEffect, useState } from "react"; import { BarChart3, ChevronDown, ChevronRight, Lightbulb, Loader2, RefreshCw, TrendingDown, TrendingUp, Zap, } from "lucide-react"; import type { AgentPerformanceSummary, AgentReflection } from "../api"; import { fetchAgentPerformance, fetchAgentReflections, triggerAgentReflection, } from "../api"; interface AgentReflectionsTabProps { agentId: string; projectId?: string; addToast: (msg: string, type?: "success" | "error") => void; } /** Format a number in milliseconds to a human-readable duration string */ function formatDuration(ms: number): string { if (ms < 1000) return `${ms}ms`; if (ms < 60_000) return `${(ms / 1000).toFixed(1)}s`; if (ms < 3_600_000) return `${Math.floor(ms / 60_000)}m`; return `${(ms / 3_600_000).toFixed(1)}h`; } /** Format a percentage value (0-1) to a percentage string */ function formatPercent(rate: number): string { return `${Math.round(rate * 100)}%`; } /** Format an ISO timestamp to a relative time string */ function relativeTime(iso: string): string { const now = Date.now(); const then = new Date(iso).getTime(); const diffMs = now - then; if (diffMs < 0) { const absDiff = Math.abs(diffMs); if (absDiff < 60_000) return "in a moment"; if (absDiff < 3_600_000) return `in ${Math.floor(absDiff / 60_000)}m`; if (absDiff < 86_400_000) return `in ${Math.floor(absDiff / 3_600_000)}h`; return `in ${Math.floor(absDiff / 86_400_000)}d`; } if (diffMs < 60_000) return "just now"; if (diffMs < 3_600_000) return `${Math.floor(diffMs / 60_000)}m ago`; if (diffMs < 86_400_000) return `${Math.floor(diffMs / 3_600_000)}h ago`; return `${Math.floor(diffMs / 86_400_000)}d ago`; } /** Get display label for a trigger type */ function getTriggerLabel(trigger: string): string { switch (trigger) { case "periodic": return "Periodic"; case "post-task": return "Post-Task"; case "manual": return "Manual"; case "user-requested": return "User Requested"; default: return trigger; } } export function AgentReflectionsTab({ agentId, projectId, addToast }: AgentReflectionsTabProps) { const [reflections, setReflections] = useState([]); const [performance, setPerformance] = useState(null); const [isLoading, setIsLoading] = useState(true); const [isReflecting, setIsReflecting] = useState(false); const [expandedReflectionId, setExpandedReflectionId] = useState(null); // Load data on mount const loadData = useCallback(async () => { try { const [reflectionsData, performanceData] = await Promise.all([ fetchAgentReflections(agentId, 20, projectId), fetchAgentPerformance(agentId, undefined, projectId), ]); setReflections(reflectionsData); setPerformance(performanceData); } catch (err: any) { addToast(`Failed to load reflections: ${err.message}`, "error"); } finally { setIsLoading(false); } }, [agentId, projectId, addToast]); useEffect(() => { void loadData(); }, [loadData]); // Handle reflect now button const handleReflectNow = async () => { setIsReflecting(true); try { await triggerAgentReflection(agentId, projectId); addToast("Reflection generated successfully", "success"); setIsLoading(true); await loadData(); } catch (err: any) { addToast(`Failed to generate reflection: ${err.message}`, "error"); } finally { setIsReflecting(false); } }; // Toggle expanded state const toggleExpanded = (id: string) => { setExpandedReflectionId((prev) => (prev === id ? null : id)); }; if (isLoading) { return (
Loading reflections...
); } // Check if performance summary has no data const hasNoPerformanceData = performance && performance.totalTasksCompleted === 0 && performance.totalTasksFailed === 0 && performance.recentReflectionCount === 0; return (
{/* Header */}

Performance & Reflections

{/* Performance Summary Grid */} {performance && !hasNoPerformanceData && (
{performance.totalTasksCompleted}
Tasks Completed
{performance.totalTasksFailed}
Tasks Failed
{formatDuration(performance.avgDurationMs)}
Avg Duration
= 0.8 ? "var(--color-success)" : performance.successRate >= 0.5 ? "var(--color-warning)" : "var(--color-error)", }} /> {formatPercent(performance.successRate)}
Success Rate
{performance.recentReflectionCount}
Reflections
)} {hasNoPerformanceData && (

No performance data yet

)} {/* Reflections List */}

Reflection History

{reflections.length === 0 ? (

No reflections yet

Trigger a reflection to get started

) : (
{reflections.map((reflection) => { const isExpanded = expandedReflectionId === reflection.id; return (
toggleExpanded(reflection.id)} role="button" tabIndex={0} onKeyDown={(e) => e.key === "Enter" && toggleExpanded(reflection.id)} >
{getTriggerLabel(reflection.trigger)} {relativeTime(reflection.timestamp)} {isExpanded ? : }
{reflection.summary}
{isExpanded && (
{reflection.insights.length > 0 && (
Insights
    {reflection.insights.map((insight, i) => (
  • {insight}
  • ))}
)} {reflection.suggestedImprovements.length > 0 && (
Suggested Improvements
    {reflection.suggestedImprovements.map((suggestion, i) => (
  • {suggestion}
  • ))}
)} {reflection.metrics && (
Metrics
{reflection.metrics.tasksCompleted !== undefined && (
Tasks: {reflection.metrics.tasksCompleted}
)} {reflection.metrics.tasksFailed !== undefined && (
Failed: {reflection.metrics.tasksFailed}
)} {reflection.metrics.avgDurationMs !== undefined && (
Avg Duration: {formatDuration(reflection.metrics.avgDurationMs)}
)} {reflection.metrics.errorCount !== undefined && (
Errors: {reflection.metrics.errorCount}
)}
)}
)}
); })}
)}
); }