"use client"; import type { Message as TMessage } from "ai"; import { AnimatePresence, motion } from "motion/react"; import { memo, useCallback, useEffect, useState } from "react"; import equal from "fast-deep-equal"; import { Markdown } from "./markdown"; import { cn } from "@/lib/utils"; import { ChevronDownIcon, ChevronUpIcon, LightbulbIcon } from "lucide-react"; import { SpinnerIcon } from "./icons"; import { ToolInvocation } from "./tool-invocation"; interface ReasoningPart { type: "reasoning"; reasoning: string; details: Array<{ type: "text"; text: string }>; } interface ReasoningMessagePartProps { part: ReasoningPart; isReasoning: boolean; } export function ReasoningMessagePart({ part, isReasoning, }: ReasoningMessagePartProps) { const [isExpanded, setIsExpanded] = useState(false); const memoizedSetIsExpanded = useCallback((value: boolean) => { setIsExpanded(value); }, []); useEffect(() => { memoizedSetIsExpanded(isReasoning); }, [isReasoning, memoizedSetIsExpanded]); return (
{isReasoning ? (
Thinking...
) : (
Reasoning
)} {isExpanded && ( {part.details.map((detail, detailIndex) => detail.type === "text" ? ( {detail.text} ) : ( "" ), )} )}
); } const PurePreviewMessage = ({ message, isLatestMessage, status, }: { message: TMessage; isLoading: boolean; status: "error" | "submitted" | "streaming" | "ready"; isLatestMessage: boolean; }) => { return (
{message.parts?.map((part, i) => { switch (part.type) { case "text": return (
{part.text}
); case "tool-invocation": const { toolName, state, args } = part.toolInvocation; const result = 'result' in part.toolInvocation ? part.toolInvocation.result : null; return ( ); case "reasoning": return ( ); default: return null; } })}
); }; export const Message = memo(PurePreviewMessage, (prevProps, nextProps) => { if (prevProps.status !== nextProps.status) return false; if (prevProps.message.annotations !== nextProps.message.annotations) return false; if (!equal(prevProps.message.parts, nextProps.message.parts)) return false; return true; });