'use client'; import cx from 'classnames'; import { AnimatePresence, motion } from 'framer-motion'; import { memo, useState } from 'react'; import type { Vote } from '@/lib/db/schema'; import { DocumentToolResult } from './document'; import { PencilEditIcon, SparklesIcon } from './icons'; import { Response } from './elements/response'; import { MessageContent } from './elements/message'; import { Tool, ToolHeader, ToolContent, ToolInput, ToolOutput, } from './elements/tool'; import { MessageActions } from './message-actions'; import { PreviewAttachment } from './preview-attachment'; import { Weather } from './weather'; import equal from 'fast-deep-equal'; import { cn, sanitizeText } from '@/lib/utils'; import { Button } from './ui/button'; import { Tooltip, TooltipContent, TooltipTrigger } from './ui/tooltip'; import { MessageEditor } from './message-editor'; import { DocumentPreview } from './document-preview'; import { MessageReasoning } from './message-reasoning'; import type { UseChatHelpers } from '@ai-sdk/react'; import type { ChatMessage } from '@/lib/types'; import { useDataStream } from './data-stream-provider'; // Type narrowing is handled by TypeScript's control flow analysis // The AI SDK provides proper discriminated unions for tool calls const PurePreviewMessage = ({ chatId, message, vote, isLoading, setMessages, regenerate, isReadonly, requiresScrollPadding, }: { chatId: string; message: ChatMessage; vote: Vote | undefined; isLoading: boolean; setMessages: UseChatHelpers['setMessages']; regenerate: UseChatHelpers['regenerate']; isReadonly: boolean; requiresScrollPadding: boolean; }) => { const [mode, setMode] = useState<'view' | 'edit'>('view'); const attachmentsFromMessage = message.parts.filter( (part) => part.type === 'file', ); useDataStream(); return (
{message.role === 'assistant' && (
)}
{attachmentsFromMessage.length > 0 && (
{attachmentsFromMessage.map((attachment) => ( ))}
)} {message.parts?.map((part, index) => { const { type } = part; const key = `message-${message.id}-part-${index}`; if (type === 'reasoning' && part.text?.trim().length > 0) { return ( ); } if (type === 'text') { if (mode === 'view') { return (
{message.role === 'user' && !isReadonly && ( Edit message )} {sanitizeText(part.text)}
); } if (mode === 'edit') { return (
); } } if (type === 'tool-getWeather') { const { toolCallId, state } = part; return ( {state === 'input-available' && ( )} {state === 'output-available' && ( } errorText={undefined} /> )} ); } if (type === 'tool-createDocument') { const { toolCallId, state } = part; return ( {state === 'input-available' && ( )} {state === 'output-available' && ( Error: {String(part.output.error)}
) : ( ) } errorText={undefined} /> )} ); } if (type === 'tool-updateDocument') { const { toolCallId, state } = part; return ( {state === 'input-available' && ( )} {state === 'output-available' && ( Error: {String(part.output.error)}
) : ( ) } errorText={undefined} /> )} ); } if (type === 'tool-requestSuggestions') { const { toolCallId, state } = part; return ( {state === 'input-available' && ( )} {state === 'output-available' && ( Error: {String(part.output.error)}
) : ( ) } errorText={undefined} /> )} ); } })} {!isReadonly && ( )} {message.role === 'user' && (
)}
); }; export const PreviewMessage = memo( PurePreviewMessage, (prevProps, nextProps) => { if (prevProps.isLoading !== nextProps.isLoading) return false; if (prevProps.message.id !== nextProps.message.id) return false; if (prevProps.requiresScrollPadding !== nextProps.requiresScrollPadding) return false; if (!equal(prevProps.message.parts, nextProps.message.parts)) return false; if (!equal(prevProps.vote, nextProps.vote)) return false; return false; }, ); export const ThinkingMessage = () => { const role = 'assistant'; return (
Thinking...
); };