Files
triqura-ecd/components/cortex/chat/clarification-card.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

134 lines
3.5 KiB
TypeScript

'use client';
/**
* ClarificationCard Component
*
* Displays clarification questions from the AI when user input is ambiguous.
* Shows:
* - Question text
* - Multiple choice options as buttons
* - Original input for context
* - Dismiss option
*
* Epic: E3 (UI Components)
* Story: E3.S3 (ClarificationCard component)
*/
import { HelpCircle, X } from 'lucide-react';
import { motion } from 'framer-motion';
import { Button } from '@/components/ui/button';
import { cn } from '@/lib/utils';
interface ClarificationCardProps {
question: string;
options: string[];
originalInput: string;
onSelectOption: (option: string) => void;
onDismiss: () => void;
}
/**
* Animation variants for the card
*/
const cardVariants = {
initial: { opacity: 0, y: 10, scale: 0.98 },
animate: {
opacity: 1,
y: 0,
scale: 1,
transition: { duration: 0.2, ease: [0.4, 0, 0.2, 1] as const },
},
exit: {
opacity: 0,
y: -5,
scale: 0.98,
transition: { duration: 0.15 },
},
};
export function ClarificationCard({
question,
options,
originalInput,
onSelectOption,
onDismiss,
}: ClarificationCardProps) {
return (
<motion.div
variants={cardVariants}
initial="initial"
animate="animate"
exit="exit"
className="rounded-xl border border-amber-200 bg-amber-50 shadow-sm overflow-hidden"
>
{/* Header */}
<div className="flex items-center justify-between px-4 py-3 border-b border-amber-200 bg-amber-100/50">
<div className="flex items-center gap-2">
<div className="flex items-center justify-center w-6 h-6 rounded-full bg-amber-200">
<HelpCircle className="w-4 h-4 text-amber-700" />
</div>
<span className="text-sm font-medium text-amber-900">
Verduidelijking nodig
</span>
</div>
{/* Dismiss button */}
<Button
variant="ghost"
size="sm"
onClick={onDismiss}
className="h-7 w-7 p-0 text-amber-600 hover:text-amber-800 hover:bg-amber-200"
>
<X className="w-4 h-4" />
</Button>
</div>
{/* Content */}
<div className="p-4 space-y-4">
{/* Original input context */}
<div className="text-xs text-amber-700">
<span className="font-medium">Je zei:</span>{' '}
<span className="italic">&ldquo;{originalInput}&rdquo;</span>
</div>
{/* Question */}
<p className="text-sm font-medium text-amber-900">{question}</p>
{/* Options grid */}
<div
className={cn(
'grid gap-2',
options.length <= 2 ? 'grid-cols-2' : 'grid-cols-1 sm:grid-cols-2'
)}
>
{options.map((option, index) => (
<Button
key={index}
variant="outline"
onClick={() => onSelectOption(option)}
className={cn(
'justify-start h-auto py-2.5 px-3',
'border-amber-300 bg-white hover:bg-amber-100 hover:border-amber-400',
'text-amber-900 text-sm font-medium',
'transition-colors'
)}
>
{option}
</Button>
))}
</div>
{/* Cancel link */}
<div className="text-center">
<button
onClick={onDismiss}
className="text-xs text-amber-600 hover:text-amber-800 underline underline-offset-2"
>
Annuleren
</button>
</div>
</div>
</motion.div>
);
}