"use client"; import { isToolUIPart, type UIMessage } from "ai"; import { memo, useMemo, useState } from "react"; import equal from "lib/equal"; import { cn, truncateString } from "lib/utils"; import type { UseChatHelpers } from "@ai-sdk/react"; import { UserMessagePart, AssistMessagePart, ToolMessagePart, ReasoningPart, FileMessagePart, SourceUrlMessagePart, } from "./message-parts"; import { ChevronDown, ChevronUp, TriangleAlertIcon } from "lucide-react"; import { Button } from "ui/button"; import { useTranslations } from "next-intl"; import { ChatMetadata } from "app-types/chat"; interface Props { message: UIMessage; prevMessage?: UIMessage; threadId?: string; isLoading?: boolean; isLastMessage?: boolean; setMessages?: UseChatHelpers["setMessages"]; sendMessage?: UseChatHelpers["sendMessage"]; className?: string; addToolResult?: UseChatHelpers["addToolResult"]; messageIndex?: number; status?: UseChatHelpers["status"]; readonly?: boolean; } const PurePreviewMessage = ({ message, prevMessage, readonly, threadId, isLoading, isLastMessage, status, className, setMessages, addToolResult, messageIndex, sendMessage, }: Props) => { const isUserMessage = useMemo(() => message.role === "user", [message.role]); const partsForDisplay = useMemo( () => message.parts.filter( (part) => !(part.type === "text" && (part as any).ingestionPreview), ), [message.parts], ); if (message.role == "system") { return null; // system message is not shown } if (!partsForDisplay.length) return null; return (
{partsForDisplay.map((part, index) => { const key = `message-${messageIndex}-part-${part.type}-${index}`; const isLastPart = index === partsForDisplay.length - 1; if (part.type === "reasoning") { return ( ); } if (isUserMessage && part.type === "text" && part.text) { return ( ); } if (part.type === "text" && !isUserMessage) { return ( ); } if (isToolUIPart(part)) { const isLast = isLastMessage && isLastPart; const isManualToolInvocation = (message.metadata as ChatMetadata)?.toolChoice == "manual" && isLastMessage && isLastPart && part.state == "input-available" && isLoading && !readonly; return ( ); } else if (part.type === "step-start") { return null; } else if (part.type === "file") { return ( ); } else if ((part as any).type === "source-url") { return ( ); } else { return
unknown part {part.type}
; } })}
); }; export const PreviewMessage = memo( PurePreviewMessage, function equalMessage(prevProps: Props, nextProps: Props) { if (prevProps.message.id !== nextProps.message.id) return false; if (prevProps.isLoading !== nextProps.isLoading) return false; if (prevProps.isLastMessage !== nextProps.isLastMessage) return false; if (prevProps.className !== nextProps.className) return false; if (nextProps.isLoading && nextProps.isLastMessage) return false; if (!equal(prevProps.message.metadata, nextProps.message.metadata)) return false; if (prevProps.message.parts.length !== nextProps.message.parts.length) { return false; } if (!equal(prevProps.message.parts, nextProps.message.parts)) { return false; } return true; }, ); export const ErrorMessage = ({ error, }: { error: Error; message?: UIMessage; }) => { const [isExpanded, setIsExpanded] = useState(false); const maxLength = 200; const t = useTranslations(); return (

{t("Chat.Error")}

{isExpanded ? error.message : truncateString(error.message, maxLength)}
{error.message.length > maxLength && ( )}

{t("Chat.thisMessageWasNotSavedPleaseTryTheChatAgain")}

); };