"use client"; import type { UseChatHelpers } from "@ai-sdk/react"; import type { Vote } from "@/lib/db/schema"; import type { ChatMessage } from "@/lib/types"; import { cn, sanitizeText } from "@/lib/utils"; import { MessageContent, MessageResponse } from "../ai-elements/message"; import { Shimmer } from "../ai-elements/shimmer"; import { Tool, ToolContent, ToolHeader, ToolInput, } from "../ai-elements/tool"; import { useDataStream } from "./data-stream-provider"; import { SparklesIcon } from "./icons"; import { MessageActions } from "./message-actions"; import { MessageReasoning } from "./message-reasoning"; import { PreviewAttachment } from "./preview-attachment"; import { Weather } from "./weather"; const PurePreviewMessage = ({ addToolApprovalResponse, chatId, message, vote, isLoading, setMessages: _setMessages, regenerate: _regenerate, isReadonly, requiresScrollPadding: _requiresScrollPadding, onEdit, }: { addToolApprovalResponse: UseChatHelpers["addToolApprovalResponse"]; chatId: string; message: ChatMessage; vote: Vote | undefined; isLoading: boolean; setMessages: UseChatHelpers["setMessages"]; regenerate: UseChatHelpers["regenerate"]; isReadonly: boolean; requiresScrollPadding: boolean; onEdit?: (message: ChatMessage) => void; }) => { const attachmentsFromMessage = message.parts.filter( (part) => part.type === "file" ); useDataStream(); const isUser = message.role === "user"; const isAssistant = message.role === "assistant"; const hasAnyContent = message.parts?.some( (part) => (part.type === "text" && part.text?.trim().length > 0) || (part.type === "reasoning" && "text" in part && part.text?.trim().length > 0) || part.type.startsWith("tool-") ); const isThinking = isAssistant && isLoading && !hasAnyContent; const attachments = attachmentsFromMessage.length > 0 && (
{attachmentsFromMessage.map((attachment) => ( ))}
); const mergedReasoning = message.parts?.reduce( (acc, part) => { if (part.type === "reasoning" && part.text?.trim().length > 0) { return { text: acc.text ? `${acc.text}\n\n${part.text}` : part.text, isStreaming: "state" in part ? part.state === "streaming" : false, rendered: false, }; } return acc; }, { text: "", isStreaming: false, rendered: false } ) ?? { text: "", isStreaming: false, rendered: false }; const parts = message.parts?.map((part, index) => { const { type } = part; const key = `message-${message.id}-part-${index}`; if (type === "reasoning") { if (!mergedReasoning.rendered && mergedReasoning.text) { mergedReasoning.rendered = true; return ( ); } return null; } if (type === "text") { return ( {sanitizeText(part.text)} ); } if (type === "tool-getWeather") { const { toolCallId, state } = part; const approvalId = (part as { approval?: { id: string } }).approval?.id; const isDenied = state === "output-denied" || (state === "approval-responded" && (part as { approval?: { approved?: boolean } }).approval?.approved === false); const widthClass = "w-[min(100%,450px)]"; if (state === "output-available") { return (
); } if (isDenied) { return (
Weather lookup was denied.
); } if (state === "approval-responded") { return (
); } return (
{(state === "input-available" || state === "approval-requested") && ( )} {state === "approval-requested" && approvalId && (
)}
); } return null; }); const actions = !isReadonly && ( onEdit(message) : undefined} vote={vote} /> ); const content = isThinking ? (
Thinking...
) : ( <> {attachments} {parts} {actions} ); return (
{isAssistant && (
)} {isAssistant ? (
{content}
) : ( content )}
); }; export const PreviewMessage = PurePreviewMessage; export const ThinkingMessage = () => { return (
Thinking...
); };