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}
);
}