import type { ReactNode } from 'react'; import { motion } from 'framer-motion'; import type { AssistantMessage } from '../lib/types'; import { tierOf } from '../lib/tiers'; import { VerdictStamp } from './VerdictStamp'; import { CopyButton } from './CopyButton'; import { useCitations } from './citationContext'; import { Icon } from './Icon'; import { Roll } from './fx'; import { cn } from '../lib/utils'; /* The footing of a reply: the Judge's verdict stamped beside it, the count of grounded sources, and copy. It arrives after the prose has landed — the stamp first, with a thump, the rest stepping in behind it. */ const ease = [0.22, 1, 0.36, 1] as const; const item = { hidden: { opacity: 0, y: 6 }, show: { opacity: 1, y: 0, transition: { duration: 0.4, ease } }, }; export function AnswerMeta({ message }: { message: AssistantMessage }) { const { railOpen, setRailOpen } = useCitations(); const count = message.citations.length; // Last line of defence. A failed turn must never reach this component, // but if one ever does, it leaves without a certainty claim attached. if (message.phase === 'failed') return null; // A refusal is not an answer: there is nothing to grade and nothing to cite. if (message.outOfScope) return null; // `null` tier means the Judge never graded this answer: the stamp then says // "Not adjudicated" rather than inventing a verdict. The answer and its // citations are real, only the adjudication is missing. const meta = message.evidenceTier ? tierOf(message.evidenceTier) : null; const adjudicated = message.confidence !== null; return ( {adjudicated ? (meta?.gloss ?? 'Independently adjudicated for groundedness and relevance') : 'The Judge was unavailable — this answer carries no confidence score'} {/* Sources */} {count > 0 && ( )} ); } /** Copied text reads `[1, 3]` rather than the wire format `[[1]][[3]]`. */ function plainCitations(content: string): string { return content .replace(/\]\]\s*\[\[/g, ', ') .replace(/\[\[(\d+(?:\s*,\s*\d+)*)\]\]/g, '[$1]'); } /** A hairline tooltip that appears on hover or keyboard focus. */ function Tip({ children }: { children: ReactNode }) { return ( {children} ); }