/** * ThinkingBlock Component * Displays LLM thinking/reasoning content in a collapsible, animated block * Shows duration, pulsing animation during streaming, and expandable content */ "use client"; import { useState } from "react"; import { ChevronDown, Brain } from "lucide-react"; import { motion, AnimatePresence } from "framer-motion"; import { Collapsible, CollapsibleContent, CollapsibleTrigger, } from "@/components/ui/collapsible"; import { cn } from "@/lib/utils"; export interface ThinkingPart { type: "thinking"; content: string; startTime?: number; endTime?: number; } interface ThinkingBlockProps { part: ThinkingPart; isStreaming?: boolean; } /** * Formats duration in milliseconds to human-readable format */ function formatDuration(ms: number): string { if (ms < 1000) { return `${ms}ms`; } const seconds = (ms / 1000).toFixed(1); return `${seconds}s`; } /** * Calculates duration from start and end times */ function calculateDuration( startTime?: number, endTime?: number ): number | null { if (!startTime) return null; const end = endTime || Date.now(); return end - startTime; } export function ThinkingBlock({ part, isStreaming = false, }: ThinkingBlockProps) { const [isOpen, setIsOpen] = useState(false); const duration = calculateDuration(part.startTime, part.endTime); const isComplete = !!part.endTime; const isPulsing = isStreaming || !isComplete; return (
{/* Pulsing Brain Icon */} {/* Label */} {isPulsing ? "Thinking..." : "Thought Process"} {/* Duration Badge */} {duration !== null && ( {formatDuration(duration)} )} {/* Streaming Indicator Dots */} {isPulsing && ( {[0, 1, 2].map(i => ( ))} )}
{/* Chevron Toggle Icon */}
{isOpen && (
{/* Content Area */}
{part.content || ( Thinking in progress... )}
{/* Timestamp Information (if available) */} {part.startTime && (
Started: {new Date(part.startTime).toLocaleTimeString()} {part.endTime && ( Ended: {new Date(part.endTime).toLocaleTimeString()} )}
)}
)}
); }