import React, { useState } from 'react'; import { Activity, MousePointer, Navigation, Type, Scroll, Clock, Camera, Terminal, Users, StopCircle, ChevronDown, ChevronUp, Filter, } from 'lucide-react'; import { Card, CardHeader, CardContent } from '@/components/ui/Card'; import { Badge } from '@/components/ui/Badge'; import { Button } from '@/components/ui/Button'; import { useEpisodeActions, useCurrentEpisode } from '@/hooks/useEpisode'; import { formatTimestamp, truncateText } from '@/utils/helpers'; import type { Action, ActionType } from '@/types'; interface ActionPanelProps { className?: string; } const ACTION_ICONS: Record = { navigate: , click: , extract: , scroll: , input: , wait: , screenshot: , execute_tool: , delegate: , terminate: , }; const ACTION_COLORS: Record = { navigate: 'text-blue-400 bg-blue-400/10', click: 'text-green-400 bg-green-400/10', extract: 'text-purple-400 bg-purple-400/10', scroll: 'text-yellow-400 bg-yellow-400/10', input: 'text-cyan-400 bg-cyan-400/10', wait: 'text-gray-400 bg-gray-400/10', screenshot: 'text-pink-400 bg-pink-400/10', execute_tool: 'text-orange-400 bg-orange-400/10', delegate: 'text-indigo-400 bg-indigo-400/10', terminate: 'text-red-400 bg-red-400/10', }; interface ActionItemProps { action: Action; index: number; isLast: boolean; } const ActionItem: React.FC = ({ action, index, isLast }) => { const [isExpanded, setIsExpanded] = useState(false); const iconClass = ACTION_COLORS[action.type] ?? 'text-dark-400 bg-dark-700'; return (
{/* Timeline connector */} {!isLast && (
)}
setIsExpanded(!isExpanded)} > {/* Icon */}
{ACTION_ICONS[action.type]}
{/* Content */}
{action.type} #{index + 1}
{formatTimestamp(action.timestamp)} {isExpanded ? ( ) : ( )}
{/* Quick info */}
{action.target?.selector && ( {truncateText(action.target.selector, 40)} )} {action.value && ( {' → '} {truncateText(action.value, 30)} )} {action.reasoning && !action.target?.selector && !action.value && ( {truncateText(action.reasoning, 50)} )}
{/* Confidence bar */}
0.8 ? 'bg-green-500' : action.confidence > 0.5 ? 'bg-yellow-500' : 'bg-red-500' }`} style={{ width: `${action.confidence * 100}%` }} />
{(action.confidence * 100).toFixed(0)}%
{/* Expanded details */} {isExpanded && (
{action.reasoning && (
Reasoning
{action.reasoning}
)} {action.target && (
Target
{JSON.stringify(action.target, null, 2)}
)} {action.parameters && Object.keys(action.parameters).length > 0 && (
Parameters
{JSON.stringify(action.parameters, null, 2)}
)}
Agent: {action.agentId} Confidence: {(action.confidence * 100).toFixed(1)}%
)}
); }; export const ActionPanel: React.FC = ({ className }) => { const { data: episode } = useCurrentEpisode(); const { data: actions, isLoading } = useEpisodeActions(episode?.id); const [typeFilter, setTypeFilter] = useState(null); const [showFilters, setShowFilters] = useState(false); const actionTypes = React.useMemo(() => { if (!actions) return []; return [...new Set(actions.map((a) => a.type))]; }, [actions]); const filteredActions = React.useMemo(() => { if (!actions) return []; if (!typeFilter) return actions; return actions.filter((a) => a.type === typeFilter); }, [actions, typeFilter]); const actionCounts = React.useMemo(() => { if (!actions) return {}; return actions.reduce( (acc, a) => { acc[a.type] = (acc[a.type] || 0) + 1; return acc; }, {} as Record ); }, [actions]); return ( } action={ } /> {/* Filter Bar */} {showFilters && (
Filter by type
{actionTypes.map((type) => ( ))}
)} {/* Action Stats */} {actions && actions.length > 0 && (
{Object.entries(actionCounts) .sort((a, b) => b[1] - a[1]) .slice(0, 5) .map(([type, count]) => ( {type}: {count} ))}
)} {/* Action Timeline */}
{isLoading ? (
) : !filteredActions || filteredActions.length === 0 ? (

No actions recorded

) : (
{filteredActions .slice() .reverse() .map((action, i, arr) => ( ))}
)}
{/* Current Action Highlight */} {actions && actions.length > 0 && (
Latest Action
{ACTION_ICONS[actions[actions.length - 1]!.type]}
{actions[actions.length - 1]!.type}
{actions[actions.length - 1]!.reasoning && (

{actions[actions.length - 1]!.reasoning}

)}
)}
); }; export default ActionPanel;