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:
182
lib/cortex/intent-labels.ts
Normal file
182
lib/cortex/intent-labels.ts
Normal file
@@ -0,0 +1,182 @@
|
||||
/**
|
||||
* Intent Labels & Status Styles
|
||||
*
|
||||
* Shared Dutch labels and styling for Cortex V2 UI components.
|
||||
* Used by ActionChainCard, ActionItem, and ClarificationCard.
|
||||
*
|
||||
* Epic: E3 (UI Components)
|
||||
*/
|
||||
|
||||
import type { CortexIntent, IntentActionStatus, NudgePriority, ExtractedEntities } from './types';
|
||||
|
||||
/**
|
||||
* Dutch labels for intent types
|
||||
*/
|
||||
export const INTENT_LABELS: Record<CortexIntent, string> = {
|
||||
dagnotitie: 'Dagnotitie',
|
||||
zoeken: 'Patiënt zoeken',
|
||||
overdracht: 'Overdracht',
|
||||
agenda_query: 'Agenda bekijken',
|
||||
create_appointment: 'Afspraak maken',
|
||||
cancel_appointment: 'Afspraak annuleren',
|
||||
reschedule_appointment: 'Afspraak verzetten',
|
||||
unknown: 'Onbekend',
|
||||
};
|
||||
|
||||
/**
|
||||
* Status styling for IntentAction items
|
||||
*/
|
||||
export const STATUS_STYLES: Record<
|
||||
IntentActionStatus,
|
||||
{ bg: string; border: string; text: string; icon: string }
|
||||
> = {
|
||||
pending: {
|
||||
bg: 'bg-slate-50',
|
||||
border: 'border-slate-200',
|
||||
text: 'text-slate-700',
|
||||
icon: 'text-slate-300',
|
||||
},
|
||||
confirming: {
|
||||
bg: 'bg-amber-50',
|
||||
border: 'border-amber-200',
|
||||
text: 'text-amber-700',
|
||||
icon: 'text-amber-500',
|
||||
},
|
||||
executing: {
|
||||
bg: 'bg-blue-50',
|
||||
border: 'border-blue-200',
|
||||
text: 'text-blue-700',
|
||||
icon: 'text-blue-500',
|
||||
},
|
||||
success: {
|
||||
bg: 'bg-green-50',
|
||||
border: 'border-green-200',
|
||||
text: 'text-green-700',
|
||||
icon: 'text-green-500',
|
||||
},
|
||||
failed: {
|
||||
bg: 'bg-red-50',
|
||||
border: 'border-red-200',
|
||||
text: 'text-red-700',
|
||||
icon: 'text-red-500',
|
||||
},
|
||||
skipped: {
|
||||
bg: 'bg-slate-50',
|
||||
border: 'border-slate-200',
|
||||
text: 'text-slate-400',
|
||||
icon: 'text-slate-400',
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Get confidence badge styling based on confidence score
|
||||
* >= 0.9: green (high confidence)
|
||||
* >= 0.7: amber (medium confidence)
|
||||
* < 0.7: red (low confidence)
|
||||
*/
|
||||
export function getConfidenceStyle(confidence: number): {
|
||||
bg: string;
|
||||
text: string;
|
||||
label: string;
|
||||
} {
|
||||
if (confidence >= 0.9) {
|
||||
return {
|
||||
bg: 'bg-green-50',
|
||||
text: 'text-green-700',
|
||||
label: 'Hoog',
|
||||
};
|
||||
}
|
||||
if (confidence >= 0.7) {
|
||||
return {
|
||||
bg: 'bg-amber-50',
|
||||
text: 'text-amber-700',
|
||||
label: 'Gemiddeld',
|
||||
};
|
||||
}
|
||||
return {
|
||||
bg: 'bg-red-50',
|
||||
text: 'text-red-700',
|
||||
label: 'Laag',
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Priority styling for NudgeSuggestion toasts
|
||||
*/
|
||||
export const PRIORITY_STYLES: Record<
|
||||
NudgePriority,
|
||||
{ bg: string; border: string; text: string }
|
||||
> = {
|
||||
low: {
|
||||
bg: 'bg-blue-50',
|
||||
border: 'border-blue-200',
|
||||
text: 'text-blue-700',
|
||||
},
|
||||
medium: {
|
||||
bg: 'bg-amber-50',
|
||||
border: 'border-amber-200',
|
||||
text: 'text-amber-700',
|
||||
},
|
||||
high: {
|
||||
bg: 'bg-red-50',
|
||||
border: 'border-red-200',
|
||||
text: 'text-red-700',
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Format entity summary for display in ActionItem
|
||||
*/
|
||||
export function formatEntitySummary(
|
||||
intent: CortexIntent,
|
||||
entities: ExtractedEntities
|
||||
): string {
|
||||
const parts: string[] = [];
|
||||
|
||||
// Patient name
|
||||
if (entities.patientName) {
|
||||
parts.push(`Patiënt: ${entities.patientName}`);
|
||||
}
|
||||
|
||||
// Content/query
|
||||
if (entities.content && typeof entities.content === 'string') {
|
||||
const truncated =
|
||||
entities.content.length > 50
|
||||
? `${entities.content.slice(0, 50)}...`
|
||||
: entities.content;
|
||||
parts.push(truncated);
|
||||
}
|
||||
|
||||
if (entities.query && typeof entities.query === 'string') {
|
||||
parts.push(`Zoekterm: "${entities.query}"`);
|
||||
}
|
||||
|
||||
// Date/time
|
||||
if (entities.datetime) {
|
||||
const dt = entities.datetime;
|
||||
// datetime.date is a Date object, format it
|
||||
const dateStr = dt.date ? dt.date.toLocaleDateString('nl-NL') : null;
|
||||
if (dateStr && dt.time) {
|
||||
parts.push(`${dateStr} om ${dt.time}`);
|
||||
} else if (dateStr) {
|
||||
parts.push(dateStr);
|
||||
} else if (dt.time) {
|
||||
parts.push(`om ${dt.time}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Category for dagnotitie
|
||||
if (intent === 'dagnotitie' && entities.category) {
|
||||
const categoryLabels: Record<string, string> = {
|
||||
medicatie: 'Medicatie',
|
||||
adl: 'ADL',
|
||||
gedrag: 'Gedrag',
|
||||
incident: 'Incident',
|
||||
observatie: 'Observatie',
|
||||
};
|
||||
const label = categoryLabels[entities.category as string] || entities.category;
|
||||
parts.push(`Categorie: ${label}`);
|
||||
}
|
||||
|
||||
return parts.join(' • ') || 'Geen details';
|
||||
}
|
||||
Reference in New Issue
Block a user