import React, { useEffect, useMemo, useRef } from 'react'; import LinkPreview from './LinkPreview'; import GooseResponseForm from './GooseResponseForm'; import { extractUrls } from '../utils/urlUtils'; import { formatMessageTimestamp } from '../utils/timeUtils'; import MarkdownContent from './MarkdownContent'; import ToolCallWithResponse from './ToolCallWithResponse'; import { Message, getTextContent, getToolRequests, getToolResponses, getToolConfirmationContent, createToolErrorResponseMessage, getExtensionContent, } from '../types/message'; import ToolCallConfirmation from './ToolCallConfirmation'; import MessageCopyLink from './MessageCopyLink'; import ExtensionConfirmation from './ExtensionConfirmation'; interface GooseMessageProps { messageHistoryIndex: number; message: Message; messages: Message[]; metadata?: string[]; append: (value: string) => void; appendMessage: (message: Message) => void; } export default function GooseMessage({ messageHistoryIndex, message, metadata, messages, append, appendMessage, }: GooseMessageProps) { const contentRef = useRef(null); // Extract text content from the message let textContent = getTextContent(message); // Memoize the timestamp const timestamp = useMemo(() => formatMessageTimestamp(message.created), [message.created]); // Get tool requests from the message const toolRequests = getToolRequests(message); // Extract URLs under a few conditions // 1. The message is purely text // 2. The link wasn't also present in the previous message // 3. The message contains the explicit http:// or https:// protocol at the beginning const messageIndex = messages?.findIndex((msg) => msg.id === message.id); const previousMessage = messageIndex > 0 ? messages[messageIndex - 1] : null; const previousUrls = previousMessage ? extractUrls(getTextContent(previousMessage)) : []; const urls = toolRequests.length === 0 ? extractUrls(textContent, previousUrls) : []; const toolConfirmationContent = getToolConfirmationContent(message); const hasToolConfirmation = toolConfirmationContent !== undefined; const extensionContent = getExtensionContent(message); const hasExtensionRequest = extensionContent !== undefined; // Find tool responses that correspond to the tool requests in this message const toolResponsesMap = useMemo(() => { const responseMap = new Map(); // Look for tool responses in subsequent messages if (messageIndex !== undefined && messageIndex >= 0) { for (let i = messageIndex + 1; i < messages.length; i++) { const responses = getToolResponses(messages[i]); for (const response of responses) { // Check if this response matches any of our tool requests const matchingRequest = toolRequests.find((req) => req.id === response.id); if (matchingRequest) { responseMap.set(response.id, response); } } } } return responseMap; }, [messages, messageIndex, toolRequests]); useEffect(() => { // If the message is the last message in the resumed session and has tool confirmation, it means the tool confirmation // is broken or cancelled, to contonue use the session, we need to append a tool response to avoid mismatch tool result error. if ( messageIndex === messageHistoryIndex - 1 && hasToolConfirmation && toolConfirmationContent ) { appendMessage( createToolErrorResponseMessage(toolConfirmationContent.id, 'The tool call is cancelled.') ); } if (messageIndex == messageHistoryIndex - 1 && hasExtensionRequest) { appendMessage( createToolErrorResponseMessage( extensionContent.id, 'The extension enablement is cancelled.' ) ); } }, [ messageIndex, messageHistoryIndex, hasToolConfirmation, toolConfirmationContent, appendMessage, hasExtensionRequest, // Only include enableExtensionContent if it exists extensionContent?.id, ]); return (
{textContent && (
0 ? 'rounded-b-none' : 'rounded-bl-none'}`} >
{}
{/* Only show MessageCopyLink if there's text content and no tool requests/responses */}
{toolRequests.length === 0 && (
{timestamp}
)} {textContent && message.content.every((content) => content.type === 'text') && (
)}
)} {toolRequests.length > 0 && (
{toolRequests.map((toolRequest) => ( ))}
{timestamp}
)} {hasToolConfirmation && ( )} {hasExtensionRequest && ( )}
{/* TODO(alexhancock): Re-enable link previews once styled well again */} {false && urls.length > 0 && (
{urls.map((url, index) => ( ))}
)} {/* enable or disable prompts here */} {/* NOTE from alexhancock on 1/14/2025 - disabling again temporarily due to non-determinism in when the forms show up */} {false && metadata && (
)}
); }