lead-to-policy/src/components/insurance/AIDecisionCard.tsx
Bhanu Prakash Sai Potteri 66d4c91f32 Initial import of lead-to-policy (dev env)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-30 15:28:12 +05:30

141 lines
5.1 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { useState } from 'react';
import type { CSSProperties } from 'react';
import { ChevronRight, FileText } from 'lucide-react';
import { cn } from '../../lib/cn';
import { Avatar } from '../core/Avatar';
import { ConfidenceRing } from './ConfidenceRing';
import { ReasoningBody } from './ReasoningBody';
export type Citation = string | { title: string };
export interface AIDecisionCardProps {
employee?: string;
role?: string;
activity?: string;
/** Confidence 01. @default 0.85 */
confidence?: number;
summary?: string;
reasoning?: string | null;
citations?: Citation[];
time?: string | null;
defaultExpanded?: boolean;
/** Autonomy threshold. @default 0.7 */
threshold?: number;
className?: string;
style?: CSSProperties;
}
/**
* AI DECISION CARD — Aria's signature component.
* Which AI employee acted, the activity, a confidence ring, a one-line
* reasoning summary, expandable full reasoning, and cited-knowledge chips.
*/
export function AIDecisionCard({
employee = 'Aria',
role = 'Lead Qualifier',
activity,
confidence = 0.85,
summary,
reasoning = null,
citations = [],
time = null,
defaultExpanded = false,
threshold = 0.7,
className,
style,
}: AIDecisionCardProps) {
const [open, setOpen] = useState(defaultExpanded);
const autonomous = confidence >= threshold;
const accent = autonomous ? 'var(--status-autonomous)' : 'var(--status-escalated)';
const accentSoft = autonomous ? 'var(--status-autonomous-soft)' : 'var(--status-escalated-soft)';
return (
<article
className={cn('relative bg-card rounded-lg border border-border-subtle shadow-md overflow-hidden font-sans', className)}
style={style}
>
{/* accent rail */}
<span
className={cn('absolute top-0 left-0 bottom-0 w-1', autonomous && 'bg-sunrise')}
style={autonomous ? undefined : { background: 'var(--status-escalated)' }}
/>
{/* header band — identity + confidence, tinted by autonomy state */}
<header
className="flex items-center gap-3 pl-[22px] pr-5 py-3.5 border-b border-border-subtle"
style={{ background: accentSoft }}
>
<Avatar name={employee} ai size={38} />
<div className="min-w-0 flex-1">
<div className="flex items-center gap-2 flex-wrap">
<span className="text-base font-bold text-strong leading-tight">{employee}</span>
<span className="text-[10px] font-bold uppercase tracking-[0.06em] text-faint bg-card border border-border-subtle px-[7px] py-[3px] rounded-pill">
{role}
</span>
</div>
<div className="text-xs text-muted mt-0.5 truncate">
{activity}
{time && <span className="text-faint"> · {time}</span>}
</div>
</div>
<div className="shrink-0">
<ConfidenceRing value={confidence} size={64} threshold={threshold} />
</div>
</header>
<div className="pl-[22px] pr-5 py-[18px]">
{/* status pill */}
<div
className="inline-flex items-center gap-1.5 text-2xs font-bold tracking-[0.03em] px-[11px] py-[5px] rounded-pill mb-2.5"
style={{ background: accentSoft, color: accent }}
>
<span className="w-1.5 h-1.5 rounded-full" style={{ background: accent }} />
{autonomous ? 'AUTONOMOUS DECISION' : 'ESCALATED TO HUMAN'}
</div>
{/* one-line reasoning summary */}
<p className="m-0 text-base leading-normal text-body">{summary}</p>
{/* expandable full reasoning */}
{reasoning && (
<div className="mt-3">
<button
type="button"
onClick={() => setOpen((o) => !o)}
className="inline-flex items-center gap-1.5 bg-none border-none p-0 cursor-pointer font-sans text-xs font-semibold text-link"
>
<ChevronRight size={14} className={cn('transition-transform duration-200', open && 'rotate-90')} />
{open ? 'Hide full reasoning' : 'Show full reasoning'}
</button>
{open && (
<div className="mt-2.5 px-4 py-3.5 bg-sunk rounded-lg">
<ReasoningBody text={reasoning} accent={accent} accentSoft={accentSoft} />
</div>
)}
</div>
)}
{/* citation chips */}
{citations.length > 0 && (
<div className="mt-3.5">
<div className="text-[10px] font-bold uppercase tracking-[0.06em] text-faint mb-[7px]">
Cited knowledge
</div>
<div className="flex flex-wrap gap-[7px]">
{citations.map((c, i) => (
<span
key={i}
className="inline-flex items-center gap-1.5 text-xs font-medium text-muted bg-card border border-border-default rounded-sm px-2.5 py-[5px]"
>
<FileText size={12} className="text-sunrise-500" />
{typeof c === 'string' ? c : c.title}
</span>
))}
</div>
</div>
)}
</div>
</article>
);
}