'use client'; /** * ActionChainCard Component * * Container for displaying multi-intent action chains with: * - Header showing source (AI/local) and action count * - Original user input * - Stacked ActionItem components for each action * - Collapsible AI reasoning section * - Dismiss button * * Epic: E3 (UI Components) * Story: E3.S1 (ActionChainCard component) */ import { useState } from 'react'; import { Sparkles, ChevronDown, ChevronUp, X, Cpu } from 'lucide-react'; import { motion, AnimatePresence } from 'framer-motion'; import { Button } from '@/components/ui/button'; import { cn } from '@/lib/utils'; import type { IntentChain } from '@/lib/cortex/types'; import { ActionItem } from './action-item'; interface ActionChainCardProps { chain: IntentChain; onConfirmAction: (actionId: string) => void; onSkipAction: (actionId: string) => void; onRetryAction: (actionId: string) => void; onDismissChain: () => void; } /** * Animation variants for the card container */ const containerVariants = { initial: { opacity: 0, y: 20, scale: 0.95 }, animate: { opacity: 1, y: 0, scale: 1, transition: { duration: 0.2, ease: [0.4, 0, 0.2, 1] as const }, }, exit: { opacity: 0, y: -10, scale: 0.98, transition: { duration: 0.15 }, }, }; /** * Stagger animation for action items */ const listVariants = { animate: { transition: { staggerChildren: 0.05, }, }, }; const itemVariants = { initial: { opacity: 0, x: -10 }, animate: { opacity: 1, x: 0, transition: { duration: 0.15 }, }, }; export function ActionChainCard({ chain, onConfirmAction, onSkipAction, onRetryAction, onDismissChain, }: ActionChainCardProps) { const [showReasoning, setShowReasoning] = useState(false); const actionCount = chain.actions.length; const completedCount = chain.actions.filter( (a) => a.status === 'success' || a.status === 'skipped' ).length; const isFromAI = chain.meta.source === 'ai'; const hasReasoning = !!chain.meta.aiReasoning; // Determine overall chain state for styling const getChainStatusStyle = () => { switch (chain.status) { case 'executing': return 'border-blue-300 bg-blue-50/50'; case 'completed': return 'border-green-300 bg-green-50/50'; case 'partial': return 'border-amber-300 bg-amber-50/50'; case 'failed': return 'border-red-300 bg-red-50/50'; default: return 'border-slate-200 bg-white'; } }; return ( {/* Header */}
{/* Source badge */} {isFromAI ? (
AI
) : (
Lokaal
)} {/* Action count */} {actionCount} {actionCount === 1 ? 'actie' : 'acties'} gedetecteerd {/* Progress indicator */} {chain.status === 'executing' && ( ({completedCount}/{actionCount} voltooid) )}
{/* Dismiss button */}
{/* Original input */}

“{chain.originalInput}”

{/* Action items */} {chain.actions.map((action, index) => ( onConfirmAction(action.id)} onSkip={() => onSkipAction(action.id)} onRetry={() => onRetryAction(action.id)} /> ))} {/* AI Reasoning (collapsible) */} {hasReasoning && (
{showReasoning && (
{chain.meta.aiReasoning}
)}
)} {/* Processing time footer */}

Verwerkt in {chain.meta.processingTimeMs}ms

); }