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>
This commit is contained in:
133
components/cortex/chat/clarification-card.tsx
Normal file
133
components/cortex/chat/clarification-card.tsx
Normal file
@@ -0,0 +1,133 @@
|
||||
'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">“{originalInput}”</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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user