import { cn } from '../../lib/cn'; export interface ReasoningParts { /** Lead-in prose before the first enumerated point. */ intro: string; /** The enumerated reasoning points. */ steps: string[]; } /** * Parse an AI reasoning blob into a lead-in + enumerated steps. * Handles the common "(1) … (2) …" / "1) … 2) …" inline enumeration and * "-"/"•"-prefixed bullet lines. Falls back to plain prose (no steps) for * anything else, so short / unstructured reasoning still renders cleanly. */ export function parseReasoning(raw: string): ReasoningParts { const text = (raw ?? '').trim(); if (!text) return { intro: '', steps: [] }; // Inline numbered enumeration — "(1) …" or "1) …". Only digit-in-paren forms, // so "(18-65 band)", "(WARM segment)", "60/100" never false-match. const markers = [...text.matchAll(/(?:^|\s)\(?(\d{1,2})\)\s+/g)]; if (markers.length >= 2) { const intro = text.slice(0, markers[0].index).trim(); const steps: string[] = []; for (let i = 0; i < markers.length; i++) { const start = markers[i].index! + markers[i][0].length; const end = i + 1 < markers.length ? markers[i + 1].index! : text.length; const seg = text.slice(start, end).trim().replace(/[,;]\s*$/, ''); if (seg) steps.push(seg); } return { intro, steps }; } // Newline bullets — "- ", "• ", "* ". const lines = text.split(/\n+/).map((l) => l.trim()).filter(Boolean); const isBullet = (l: string) => /^[-•*]\s+/.test(l); if (lines.filter(isBullet).length >= 2) { const introLines: string[] = []; let i = 0; while (i < lines.length && !isBullet(lines[i])) introLines.push(lines[i++]); const steps = lines.slice(i).filter(isBullet).map((l) => l.replace(/^[-•*]\s+/, '')); return { intro: introLines.join(' '), steps }; } return { intro: text, steps: [] }; } /** * Split a prose blob into sentence-sized facts for scannable rendering. * Only breaks on terminal punctuation that follows a word/paren/quote and * precedes a capital — so decimals (₹2.76), ranges (18-65), and multiples * (10-20x) never split mid-fact. Newlines are flattened first. */ function splitSentences(raw: string): string[] { return (raw ?? '') .replace(/\s*\n+\s*/g, ' ') .trim() .split(/(?<=[a-zA-Z)\]%"'])[.!?]+\s+(?=[A-Z"'(])/) .map((s) => s.trim()) .filter(Boolean); } export interface ReasoningBodyProps { text: string; /** Accent color for the step badges (matches the decision's autonomy state). */ accent?: string; accentSoft?: string; className?: string; } /** Renders an AI reasoning blob as a lead-in paragraph + numbered step list. */ export function ReasoningBody({ text, accent = 'var(--status-autonomous)', accentSoft = 'var(--status-autonomous-soft)', className, }: ReasoningBodyProps) { const { intro, steps } = parseReasoning(text); // No enumeration — split the prose into sentence-sized facts and render them // as a scannable list, so a dense wall of reasoning reads point-by-point. if (steps.length === 0) { const facts = splitSentences(text); if (facts.length < 2) { return (

{text.trim()}

); } return ( ); } return (
{intro &&

{intro}

}
    {steps.map((s, i) => (
  1. {i + 1} {s}
  2. ))}
); }