// ======================================== // ConversationCard Component // ======================================== // Card component for displaying CLI execution history items import * as React from 'react'; import { useIntl } from 'react-intl'; import { cn } from '@/lib/utils'; import { Card, CardContent } from '@/components/ui/Card'; import { Badge } from '@/components/ui/Badge'; import { Button } from '@/components/ui/Button'; import { MoreVertical, Eye, Trash2, Copy, Clock, Timer, Hash, MessagesSquare, Folder, } from 'lucide-react'; import { DropdownMenu, DropdownMenuTrigger, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator, } from '@/components/ui/Dropdown'; import type { CliExecution } from '@/lib/api'; export interface ConversationCardProps { /** Execution data */ execution: CliExecution; /** Called when view action is triggered */ onView?: (execution: CliExecution) => void; /** Called when delete action is triggered */ onDelete?: (id: string) => void; /** Called when card is clicked */ onClick?: (execution: CliExecution) => void; /** Optional className */ className?: string; /** Disabled state for actions */ actionsDisabled?: boolean; } // Status configuration const statusConfig = { success: { variant: 'success' as const, icon: 'check-circle', }, error: { variant: 'destructive' as const, icon: 'x-circle', }, timeout: { variant: 'warning' as const, icon: 'clock', }, }; /** * Format duration to human readable string */ function formatDuration(ms: number): string { const seconds = Math.floor(ms / 1000); if (seconds < 60) return `${seconds}s`; const minutes = Math.floor(seconds / 60); const remainingSeconds = seconds % 60; return `${minutes}m ${remainingSeconds}s`; } /** * Get time ago string */ function getTimeAgo(dateString: string): string { const date = new Date(dateString); const now = new Date(); const seconds = Math.floor((now.getTime() - date.getTime()) / 1000); if (seconds < 60) return 'just now'; const minutes = Math.floor(seconds / 60); if (minutes < 60) return `${minutes}m ago`; const hours = Math.floor(minutes / 60); if (hours < 24) return `${hours}h ago`; const days = Math.floor(hours / 24); return `${days}d ago`; } /** * ConversationCard component for displaying CLI execution history */ export function ConversationCard({ execution, onView, onDelete, onClick, className, actionsDisabled = false, }: ConversationCardProps) { const { formatMessage } = useIntl(); const [copied, setCopied] = React.useState(false); const status = statusConfig[execution.status] || statusConfig.error; const handleCopyId = async (e: React.MouseEvent) => { e.stopPropagation(); try { await navigator.clipboard.writeText(execution.id); setCopied(true); setTimeout(() => setCopied(false), 2000); } catch { console.error('Failed to copy ID'); } }; const handleCardClick = (e: React.MouseEvent) => { // Don't trigger if clicking on dropdown if ((e.target as HTMLElement).closest('[data-radix-popper-content-wrapper]')) { return; } onClick?.(execution); }; const handleAction = ( e: React.MouseEvent, action: 'view' | 'delete' | 'copy' ) => { e.stopPropagation(); switch (action) { case 'view': onView?.(execution); break; case 'delete': onDelete?.(execution.id); break; case 'copy': handleCopyId(e); break; } }; return (
{/* Main content */}
{/* Header row */}
{execution.tool} {execution.mode || 'analysis'} {execution.turn_count && execution.turn_count > 1 && ( {execution.turn_count} )} {execution.sourceDir && execution.sourceDir !== '.' && ( {execution.sourceDir} )} {status.icon === 'check-circle' && '✓'} {status.icon === 'x-circle' && '✗'} {status.icon === 'clock' && '⏱'} {execution.status}
{/* Prompt preview */}

{execution.prompt_preview}

{/* Meta info */}
{getTimeAgo(execution.timestamp)} {formatDuration(execution.duration_ms)} {execution.id.substring(0, 8)}...
{/* Actions dropdown */} handleAction(e, 'copy')}> {copied ? formatMessage({ id: 'history.actions.copied' }) : formatMessage({ id: 'history.actions.copyId' })} handleAction(e, 'view')}> {formatMessage({ id: 'history.actions.view' })} handleAction(e, 'delete')} className="text-destructive focus:text-destructive" > {formatMessage({ id: 'history.actions.delete' })}
); }