import React, { useState } from "react"; import { motion, AnimatePresence } from "framer-motion"; import { Play, Clock, Hash, Bot } from "lucide-react"; import { Card, CardContent } from "@/components/ui/card"; import { Badge } from "@/components/ui/badge"; import { Pagination } from "@/components/ui/pagination"; import { useTranslation } from "react-i18next"; import { cn } from "@/lib/utils"; import { formatISOTimestamp } from "@/lib/date-utils"; import type { AgentRunWithMetrics } from "@/lib/api"; import { AGENT_ICONS } from "./CCAgents"; import { useTabState } from "@/hooks/useTabState"; interface AgentRunsListProps { /** * Array of agent runs to display */ runs: AgentRunWithMetrics[]; /** * Callback when a run is clicked */ onRunClick?: (run: AgentRunWithMetrics) => void; /** * Optional className for styling */ className?: string; } const ITEMS_PER_PAGE = 5; /** * AgentRunsList component - Displays a paginated list of agent execution runs * * @example * console.log('Selected:', run)} * /> */ export const AgentRunsList: React.FC = ({ runs, onRunClick, className, }) => { const { t, i18n } = useTranslation(); const [currentPage, setCurrentPage] = useState(1); const { createAgentTab } = useTabState(); const currentLocale = i18n.language; // Calculate pagination const totalPages = Math.ceil(runs.length / ITEMS_PER_PAGE); const startIndex = (currentPage - 1) * ITEMS_PER_PAGE; const endIndex = startIndex + ITEMS_PER_PAGE; const currentRuns = runs.slice(startIndex, endIndex); // Reset to page 1 if runs change React.useEffect(() => { setCurrentPage(1); }, [runs.length]); const renderIcon = (iconName: string) => { const Icon = AGENT_ICONS[iconName as keyof typeof AGENT_ICONS] || Bot; return ; }; const formatDuration = (ms?: number) => { if (!ms) return "N/A"; const seconds = Math.floor(ms / 1000); const isZhCN = currentLocale.startsWith('zh'); if (seconds < 60) { return isZhCN ? `${seconds}秒` : `${seconds}s`; } const minutes = Math.floor(seconds / 60); const remainingSeconds = seconds % 60; if (isZhCN) { return `${minutes}分${remainingSeconds}秒`; } return `${minutes}m ${remainingSeconds}s`; }; const formatTokens = (tokens?: number) => { if (!tokens) return "0"; if (tokens >= 1000) { return `${(tokens / 1000).toFixed(1)}k`; } return tokens.toString(); }; const getStatusLabel = (status: string) => { const statusLabels: Record = { completed: t('agents.statusCompleted', 'Completed'), running: t('agents.statusRunning', 'Running'), failed: t('agents.statusFailed', 'Failed'), pending: t('agents.statusPending', 'Pending') }; return statusLabels[status] || status; }; const handleRunClick = (run: AgentRunWithMetrics) => { // If there's a callback, use it (for full-page navigation) if (onRunClick) { onRunClick(run); } else if (run.id) { // Otherwise, open in new tab createAgentTab(run.id.toString(), run.agent_name); } }; if (runs.length === 0) { return (

{t('agents.noExecutionHistory')}

); } return ( <>
{currentRuns.map((run, index) => ( handleRunClick(run)} >
{renderIcon(run.agent_icon)}

{run.agent_name}

{run.status === "running" && (
{getStatusLabel('running')}
)}

{run.task}

{formatISOTimestamp(run.created_at, currentLocale)}
{run.metrics?.duration_ms && ( {formatDuration(run.metrics.duration_ms)} )} {run.metrics?.total_tokens && (
{formatTokens(run.metrics.total_tokens)}
)}
{getStatusLabel(run.status || 'pending')}
))}
{/* Pagination */} {totalPages > 1 && (
)}
); };