import React, { useEffect, useMemo, useState } from "react"; import { motion } from "framer-motion"; import type { ThinkingTimelineEntry } from "../../../types/chatStream"; interface AssistantThinkingSummaryProps { label: string; timeline?: ThinkingTimelineEntry[]; isActive?: boolean; defaultExpanded?: boolean; } const AssistantThinkingSummary: React.FC = ({ label, timeline = [], isActive = false, defaultExpanded = false, }) => { const [isExpandedState, setIsExpanded] = useState(defaultExpanded); const [dots, setDots] = useState(0); useEffect(() => { if (!isActive) { setDots(0); return; } const id = setInterval(() => setDots((d) => (d + 1) % 4), 400); return () => clearInterval(id); }, [isActive, label]); const visibleTimeline = useMemo( () => timeline.filter((entry) => entry.kind !== 'reasoning' || entry.text.trim()), [timeline] ); const groupedTimeline = useMemo(() => { const groups: Array< | { kind: 'tool_group'; id: string; entries: Extract[] } | Exclude > = []; for (const entry of visibleTimeline) { if (entry.kind === 'tool') { const lastGroup = groups[groups.length - 1]; if (lastGroup && lastGroup.kind === 'tool_group') { lastGroup.entries.push(entry); } else { groups.push({ kind: 'tool_group', id: `tool-group-${entry.id}`, entries: [entry], }); } continue; } groups.push(entry); } return groups; }, [visibleTimeline]); const renderReasoningBlocks = (text: string) => { const blocks = text .split(/\n{2,}/) .map((block) => block.trim()) .filter(Boolean); return blocks.map((block, index) => { const boldHeadingMatch = block.match(/^\*\*(.+?)\*\*$/s); const markdownHeadingMatch = block.match(/^#{1,6}\s+(.+)$/); if (boldHeadingMatch || markdownHeadingMatch) { const headingText = (boldHeadingMatch?.[1] || markdownHeadingMatch?.[1] || '').trim(); return (
{headingText}
); } return (
{block}
); }); }; const isCollapsible = !isActive; const isExpanded = isActive || isExpandedState; return (
{isExpanded && groupedTimeline.length > 0 && (
{groupedTimeline.map((entry) => { if (entry.kind === 'reasoning') { return ( {renderReasoningBlocks(entry.text)} ); } if (entry.kind === 'approval') { return ( {entry.label} ); } if (entry.kind === 'tool_group') { return ( {entry.entries.map((toolEntry) => (
{toolEntry.label}
))}
); } return null; })}
)}
); }; export default React.memo(AssistantThinkingSummary);