import { useMemo, useRef } from 'react'; import ImagePreview from './ImagePreview'; import { formatMessageTimestamp } from '../utils/timeUtils'; import MarkdownContent from './MarkdownContent'; import ThinkingContent from './ThinkingContent'; import ToolCallWithResponse from './ToolCallWithResponse'; import { getTextAndImageContent, getThinkingContent, getToolRequests, getToolResponses, getToolConfirmationContent, getElicitationContent, getPendingToolConfirmationIds, getAnyToolConfirmationData, ToolConfirmationData, NotificationEvent, type Message, } from '../types/message'; import ToolCallConfirmation from './ToolCallConfirmation'; import ElicitationRequest from './ElicitationRequest'; import MessageCopyLink from './MessageCopyLink'; import MessageUsageStats from './MessageUsageStats'; import { cn } from '../utils'; import { identifyConsecutiveToolCalls, shouldHideTimestamp } from '../utils/toolCallChaining'; interface GooseMessageProps { sessionId: string; message: Message; messages: Message[]; metadata?: string[]; toolCallNotifications: Map; append: (value: string) => void; isStreaming: boolean; submitElicitationResponse?: ( elicitationId: string, userData: Record ) => Promise; } export default function GooseMessage({ sessionId, message, messages, toolCallNotifications, append, isStreaming, submitElicitationResponse, }: GooseMessageProps) { const contentRef = useRef(null); const { textContent: displayText, imagePaths } = getTextAndImageContent(message); const thinkingContent = getThinkingContent(message); const timestamp = useMemo(() => formatMessageTimestamp(message.created), [message.created]); const toolRequests = getToolRequests(message); const messageIndex = messages.findIndex((msg) => msg.id === message.id); const toolConfirmationContent = getToolConfirmationContent(message); const elicitationContent = getElicitationContent(message); const findConfirmationForToolAcrossMessages = ( toolRequestId: string ): ToolConfirmationData | undefined => { for (const msg of messages) { const confirmationData = getAnyToolConfirmationData(msg); if (confirmationData && confirmationData.id === toolRequestId) { return confirmationData; } } return undefined; }; const toolCallChains = useMemo(() => identifyConsecutiveToolCalls(messages), [messages]); const hideTimestamp = useMemo( () => shouldHideTimestamp(messageIndex, toolCallChains), [messageIndex, toolCallChains] ); const hasToolConfirmation = toolConfirmationContent !== undefined; const hasElicitation = elicitationContent !== undefined; const elicitationData = elicitationContent?.data.actionType === 'elicitation' ? (elicitationContent.data as typeof elicitationContent.data & { isSubmitted?: boolean; isCancelled?: boolean; }) : undefined; const toolConfirmationShownInline = useMemo(() => { if (!toolConfirmationContent) return false; const confirmationData = getAnyToolConfirmationData(message); if (!confirmationData) return false; for (const msg of messages) { const requests = getToolRequests(msg); if (requests.some((req) => req.id === confirmationData.id)) { return true; } } return false; }, [toolConfirmationContent, message, messages]); const toolResponsesMap = useMemo(() => { const responseMap = new Map(); if (messageIndex !== undefined && messageIndex >= 0) { for (let i = messageIndex + 1; i < messages.length; i++) { const responses = getToolResponses(messages[i]); for (const response of responses) { const matchingRequest = toolRequests.find((req) => req.id === response.id); if (matchingRequest) { responseMap.set(response.id, response); } } } } return responseMap; }, [messages, messageIndex, toolRequests]); const pendingConfirmationIds = getPendingToolConfirmationIds(messages); return (
{thinkingContent && ( )} {(displayText.trim() || imagePaths.length > 0) && (
{displayText.trim() && (
)} {imagePaths.length > 0 && (
{imagePaths.map((imagePath, index) => ( ))}
)} {toolRequests.length === 0 && (
{!isStreaming && (
{timestamp}
)} {message.content.every((content) => content.type === 'text') && !isStreaming && (
)} {!isStreaming && message.metadata.usage && (
)}
)}
)} {toolRequests.length > 0 && (
{toolRequests.map((toolRequest) => { const hasResponse = toolResponsesMap.has(toolRequest.id); const isPending = pendingConfirmationIds.has(toolRequest.id); const confirmationContent = findConfirmationForToolAcrossMessages(toolRequest.id); const isApprovalClicked = confirmationContent && !isPending && hasResponse; return (
); })}
{!isStreaming && !hideTimestamp && timestamp}
{!isStreaming && message.metadata.usage && (
)}
)} {hasToolConfirmation && !toolConfirmationShownInline && ( )} {hasElicitation && submitElicitationResponse && ( )}
); }