Files
triqura-ecd/components/cortex/chat/action-item.tsx
colinislit 42d64761ec feat(cortex): Epic 3 - UI Components complete
Epic 3 delivers the visual layer for multi-intent flows and clarification:

E3.S1 - ActionChainCard (3 SP)
- Container for multi-intent chain display
- Header with AI/Local source badge and action count
- Original user input context
- Stacked ActionItems with Framer Motion animations
- Collapsible AI reasoning section
- Status-dependent styling (pending/executing/completed/partial/failed)

E3.S2 - ActionItem (2 SP)
- 6 status icons (pending, confirming, executing, success, failed, skipped)
- Dutch intent labels via shared intent-labels.ts
- Confidence badge with color coding (green ≥90%, amber ≥70%, red <70%)
- Entity summary formatting
- Confirmation buttons for confirming status
- Retry button for failed + recoverable actions

E3.S3 - ClarificationCard (2 SP)
- Amber-themed styling for question context
- Header with HelpCircle icon
- Original user input display
- Responsive options grid (2 cols desktop, 1 col mobile)
- Dismiss button and cancel link
- Framer Motion entry/exit animations

E3.S4 - ProcessingIndicator (1 SP)
- 3 variants: spinner, skeleton, pulse
- 3 sizes: sm, md, lg
- Configurable message
- InlineSpinner export for buttons

Integration in ChatPanel (v4.0):
- V2 store hooks for activeChain, pendingClarification
- Event handlers for confirm/skip/retry/dismiss
- Conditional rendering with AnimatePresence

New files:
- lib/cortex/intent-labels.ts
- components/cortex/chat/action-chain-card.tsx
- components/cortex/chat/action-item.tsx
- components/cortex/chat/clarification-card.tsx
- components/cortex/chat/processing-indicator.tsx

Modified:
- components/cortex/chat/chat-panel.tsx (v3.0 → v4.0)
- docs/intent/bouwplan-cortex-v2.md (v1.2 → v1.3)

Progress: 35/48 SP (73%) - E0, E1, E2, E3 complete

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-01 12:02:15 +01:00

167 lines
4.6 KiB
TypeScript

'use client';
/**
* ActionItem Component
*
* Displays a single action in a multi-intent chain with:
* - Sequence number badge
* - Status icon (pending, confirming, executing, success, failed, skipped)
* - Intent label (Dutch)
* - Confidence badge with color coding
* - Entity summary
* - Confirmation/retry buttons
*
* Epic: E3 (UI Components)
* Story: E3.S2 (ActionItem sub-component)
*/
import {
Circle,
AlertCircle,
Loader2,
Check,
X,
RotateCcw,
} from 'lucide-react';
import { Button } from '@/components/ui/button';
import { cn } from '@/lib/utils';
import type { IntentAction, IntentActionStatus } from '@/lib/cortex/types';
import {
INTENT_LABELS,
STATUS_STYLES,
getConfidenceStyle,
formatEntitySummary,
} from '@/lib/cortex/intent-labels';
interface ActionItemProps {
action: IntentAction;
sequence: number;
totalActions: number;
onConfirm: () => void;
onSkip: () => void;
onRetry: () => void;
}
/**
* Status icons mapped to each action status
*/
const STATUS_ICONS: Record<IntentActionStatus, React.ReactNode> = {
pending: <Circle className="w-4 h-4" />,
confirming: <AlertCircle className="w-4 h-4" />,
executing: <Loader2 className="w-4 h-4 animate-spin" />,
success: <Check className="w-4 h-4" />,
failed: <X className="w-4 h-4" />,
skipped: <X className="w-4 h-4" />,
};
export function ActionItem({
action,
sequence,
totalActions,
onConfirm,
onSkip,
onRetry,
}: ActionItemProps) {
const statusStyle = STATUS_STYLES[action.status];
const confidenceStyle = getConfidenceStyle(action.confidence);
const intentLabel = INTENT_LABELS[action.intent] || action.intent;
const entitySummary = formatEntitySummary(action.intent, action.entities);
const isConfirming = action.status === 'confirming';
const isFailed = action.status === 'failed';
const isCompleted = action.status === 'success' || action.status === 'skipped';
return (
<div
className={cn(
'rounded-lg border p-3 transition-colors',
statusStyle.bg,
statusStyle.border
)}
>
{/* Main row: sequence, icon, label, confidence */}
<div className="flex items-center gap-3">
{/* Sequence badge */}
<div
className={cn(
'flex-shrink-0 w-6 h-6 rounded-full flex items-center justify-center',
'text-xs font-medium',
isCompleted ? 'bg-slate-200 text-slate-500' : 'bg-slate-100 text-slate-700'
)}
>
{sequence}
</div>
{/* Status icon */}
<div className={cn('flex-shrink-0', statusStyle.icon)}>
{STATUS_ICONS[action.status]}
</div>
{/* Intent label */}
<div className="flex-1 min-w-0">
<span className={cn('text-sm font-medium', statusStyle.text)}>
{intentLabel}
</span>
</div>
{/* Confidence badge */}
<div
className={cn(
'flex-shrink-0 px-2 py-0.5 rounded-full text-xs font-medium',
confidenceStyle.bg,
confidenceStyle.text
)}
>
{Math.round(action.confidence * 100)}%
</div>
</div>
{/* Entity summary */}
<div className="mt-2 ml-9 text-sm text-slate-600">{entitySummary}</div>
{/* Confirmation message and buttons */}
{isConfirming && (
<div className="mt-3 ml-9 space-y-2">
{action.confirmationMessage && (
<p className="text-sm text-amber-800">{action.confirmationMessage}</p>
)}
<div className="flex items-center gap-2">
<Button
size="sm"
variant="default"
onClick={onConfirm}
className="bg-teal-600 hover:bg-teal-700"
>
<Check className="w-3 h-3 mr-1" />
Bevestigen
</Button>
<Button size="sm" variant="ghost" onClick={onSkip}>
Overslaan
</Button>
</div>
</div>
)}
{/* Error state with retry option */}
{isFailed && action.error && (
<div className="mt-3 ml-9 space-y-2">
<p className="text-sm text-red-700">{action.error.message}</p>
{action.error.recoverable && (
<Button size="sm" variant="outline" onClick={onRetry}>
<RotateCcw className="w-3 h-3 mr-1" />
Opnieuw proberen
</Button>
)}
</div>
)}
{/* Completion time (optional, for completed actions) */}
{isCompleted && action.completedAt && (
<div className="mt-1 ml-9 text-xs text-slate-400">
{action.status === 'success' ? 'Voltooid' : 'Overgeslagen'}
</div>
)}
</div>
);
}