import { useState } from "react"; import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"; import { agentKpisApi, analyticsApi, experimentsApi, type AgentKpi, type KpiObservation, type AgentExperiment, type ObservationCreateRequest, type ExperimentCreateRequest, } from "../api/agentKpis"; import { queryKeys } from "../lib/queryKeys"; import { useToast } from "../context/ToastContext"; import { cn, formatCents } from "../lib/utils"; import { Button } from "@/components/ui/button"; import { Card } from "@/components/ui/card"; import { Badge } from "@/components/ui/badge"; import { Input } from "@/components/ui/input"; import { Textarea } from "@/components/ui/textarea"; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from "@/components/ui/dialog"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select"; import { BarChart3, TrendingUp, DollarSign, Clock, Activity, Plus, Trash2, Eye, FlaskConical, } from "lucide-react"; const experimentStatusColors: Record = { draft: "bg-gray-100 text-gray-800 dark:bg-gray-900/30 dark:text-gray-300", running: "bg-blue-100 text-blue-800 dark:bg-blue-900/30 dark:text-blue-300", paused: "bg-amber-100 text-amber-800 dark:bg-amber-900/30 dark:text-amber-300", completed: "bg-green-100 text-green-800 dark:bg-green-900/30 dark:text-green-300", cancelled: "bg-red-100 text-red-800 dark:bg-red-900/30 dark:text-red-300", }; const severityColors: Record = { info: "bg-blue-100 text-blue-800 dark:bg-blue-900/30 dark:text-blue-300", warning: "bg-amber-100 text-amber-800 dark:bg-amber-900/30 dark:text-amber-300", critical: "bg-red-100 text-red-800 dark:bg-red-900/30 dark:text-red-300", }; interface AgentPerformanceTabProps { agentId: string; companyId: string; } export function AgentPerformanceTab({ agentId, companyId }: AgentPerformanceTabProps) { const queryClient = useQueryClient(); const { pushToast } = useToast(); const [obsDialogOpen, setObsDialogOpen] = useState(false); const [obsForm, setObsForm] = useState<{ title: string; content: string; severity: "info" | "warning" | "critical" }>({ title: "", content: "", severity: "info" }); const [deleteObsConfirm, setDeleteObsConfirm] = useState(null); const [expDialogOpen, setExpDialogOpen] = useState(false); const [expForm, setExpForm] = useState({ name: "", description: "", hypothesis: "" }); const [deleteExpConfirm, setDeleteExpConfirm] = useState(null); const kpisQuery = useQuery({ queryKey: queryKeys.agentKpis.list(agentId), queryFn: () => agentKpisApi.list(agentId, { limit: 20 }), }); const observationsQuery = useQuery({ queryKey: queryKeys.observations.list(companyId), queryFn: () => analyticsApi.listObservations(companyId), }); const experimentsQuery = useQuery({ queryKey: queryKeys.experiments.list(agentId), queryFn: () => experimentsApi.list(agentId), }); const createObsMutation = useMutation({ mutationFn: (data: ObservationCreateRequest) => analyticsApi.createObservation(companyId, data), onSuccess: () => { queryClient.invalidateQueries({ queryKey: queryKeys.observations.list(companyId) }); pushToast({ title: "Observation created" }); setObsDialogOpen(false); setObsForm({ title: "", content: "", severity: "info" }); }, onError: () => pushToast({ tone: "warn", title: "Failed to create observation" }), }); const deleteObsMutation = useMutation({ mutationFn: (id: string) => analyticsApi.deleteObservation(companyId, id), onSuccess: () => { queryClient.invalidateQueries({ queryKey: queryKeys.observations.list(companyId) }); pushToast({ title: "Observation deleted" }); setDeleteObsConfirm(null); }, onError: () => pushToast({ tone: "warn", title: "Failed to delete observation" }), }); const createExpMutation = useMutation({ mutationFn: (data: ExperimentCreateRequest) => experimentsApi.create(agentId, data), onSuccess: () => { queryClient.invalidateQueries({ queryKey: queryKeys.experiments.list(agentId) }); pushToast({ title: "Experiment created" }); setExpDialogOpen(false); setExpForm({ name: "", description: "", hypothesis: "" }); }, onError: () => pushToast({ tone: "warn", title: "Failed to create experiment" }), }); const deleteExpMutation = useMutation({ mutationFn: (id: string) => experimentsApi.delete(agentId, id), onSuccess: () => { queryClient.invalidateQueries({ queryKey: queryKeys.experiments.list(agentId) }); pushToast({ title: "Experiment deleted" }); setDeleteExpConfirm(null); }, onError: () => pushToast({ tone: "warn", title: "Failed to delete experiment" }), }); const kpis = kpisQuery.data ?? []; const observations = (observationsQuery.data ?? []).filter( (o) => !o.agentId || o.agentId === agentId, ); const experiments = experimentsQuery.data ?? []; // Compute summary values from KPIs const completionRates = kpis.filter((k) => k.completionRate != null).map((k) => k.completionRate!); const avgCompletion = completionRates.length > 0 ? completionRates.reduce((a, b) => a + b, 0) / completionRates.length : null; const costs = kpis.filter((k) => k.costCents != null).map((k) => k.costCents!); const avgCost = costs.length > 0 ? costs.reduce((a, b) => a + b, 0) / costs.length : null; const durations = kpis.filter((k) => k.durationMs != null).map((k) => k.durationMs!); const avgDuration = durations.length > 0 ? durations.reduce((a, b) => a + b, 0) / durations.length : null; const totalRuns = kpis.length; function formatDuration(ms: number): string { if (ms < 1000) return `${Math.round(ms)}ms`; if (ms < 60_000) return `${(ms / 1000).toFixed(1)}s`; return `${(ms / 60_000).toFixed(1)}m`; } return (
{/* KPI Summary Cards */}
{/* Recent KPIs */}

Recent KPI Entries

{kpis.length === 0 ? ( ) : (
{kpis.map((kpi) => ( ))}
Date Completion Cost Duration Errors
{new Date(kpi.createdAt).toLocaleDateString()} {kpi.completionRate != null ? `${Math.round(kpi.completionRate * 100)}%` : "--"} {kpi.costCents != null ? formatCents(kpi.costCents) : "--"} {kpi.durationMs != null ? formatDuration(kpi.durationMs) : "--"} {kpi.errorCount > 0 ? ( {kpi.errorCount} ) : ( "0" )}
)}
{/* Observations */}

Observations

{observations.length === 0 ? ( ) : (
{observations.map((obs) => (

{obs.title}

{obs.severity} {obs.observerType}

{obs.content}

))}
)}
{/* Experiments */}

Experiments

{experiments.length === 0 ? ( ) : (
{experiments.map((exp) => (

{exp.name}

{exp.status}
{exp.description && (

{exp.description}

)} {exp.hypothesis && (

Hypothesis: {exp.hypothesis}

)} {exp.result && (

Result: {exp.result}

)}
))}
)}
{/* Observation Dialog */} !open && setObsDialogOpen(false)}> Add Observation Record an observation about this agent's performance.
setObsForm({ ...obsForm, title: e.target.value })} placeholder="Observation title" />