/** * @file MessageComponentBox.tsx * @description This file defines the MessageComponentBox component, which renders the content * of an AssistantMessage. It supports rendering various message types such as grounded text, * normal text, and follow-up questions. The component uses React and MobX for state management * and includes functionality for handling citation and follow-up actions, as well as displaying * agent processing information. */ import React, { useState } from 'react'; import { observer } from 'mobx-react'; import { AssistantMessage, Citation, MessageContent, PROCESSING_TYPE, ProcessingInfo, TEXT_TYPE } from '../types/types'; import ReactMarkdown from 'react-markdown'; import remarkGfm from 'remark-gfm'; /** * Props for the MessageComponentBox. * @interface MessageComponentProps * @property {AssistantMessage} message - The message data to display. * @property {number} index - The index of the message. * @property {Function} onFollowUpClick - Callback to handle follow-up question clicks. * @property {Function} onCitationClick - Callback to handle citation clicks. * @property {Function} updateMessageCitations - Function to update message citations. */ interface MessageComponentProps { message: AssistantMessage; onFollowUpClick: (question: string) => void; onCitationClick: (citation: Citation) => void; updateMessageCitations: (index: number, citations: Citation[]) => void; } /** * MessageComponentBox displays the content of an AssistantMessage including text, citations, * processing information, and follow-up questions. * @param {MessageComponentProps} props - The props for the component. */ const MessageComponentBox: React.FC = ({ message, onFollowUpClick, onCitationClick }) => { // State for managing whether the dropdown is open or closed for processing info const [dropdownOpen, setDropdownOpen] = useState(false); /** * Renders the content of the message based on the type (e.g., grounded text, normal text). * @param {MessageContent} item - The content item to render. * @returns {JSX.Element} JSX element rendering the content. */ const renderContent = (item: MessageContent) => { const i = item.index; // Handle grounded text with citations if (item.type === TEXT_TYPE.GROUNDED) { const citation_ids = item.citation_ids || []; return ( ( {children} {citation_ids.map((id, idx) => { const citation = message.citations?.find(c => c.citation_id === id); if (!citation) return null; return ( ); })}
), }}> {item.text}
); } // Handle normal text else if (item.type === TEXT_TYPE.NORMAL) { return ( {item.text} ); } // Handle query type content else if ('query' in item) { return ( {JSON.stringify(item.query)} ); } // Fallback for any other content type else { return ( {item.text} ); } }; // Check if the message contains processing information (thoughts/actions) const hasProcessingInfo = message.processing_info && message.processing_info.length > 0; /** * Renders processing information such as thoughts or actions during message handling. * @param {ProcessingInfo} info - The processing information to render. * @returns {JSX.Element | null} JSX element rendering the processing info or null. */ const renderProcessingInfo = (info: ProcessingInfo) => { if (info.type === PROCESSING_TYPE.THOUGHT) { return (
Thought: {info.content}
); } else if (info.type === PROCESSING_TYPE.ACTION) { return (
Action: {info.content}
); } return null; }; /** * Formats the follow-up question text to ensure proper capitalization * @param {string} question - The original question text * @returns {string} The formatted question */ const formatFollowUpQuestion = (question: string) => { // Only capitalize first letter if needed and preserve the rest if (!question) return ''; const formattedQuestion = question.charAt(0).toUpperCase() + question.slice(1).toLowerCase(); return formattedQuestion; }; return (
{/* Processing Information Dropdown */} {hasProcessingInfo && (
{dropdownOpen &&
{message.processing_info.map(renderProcessingInfo)}
}
)} {/* Message Content */}
{message.content && message.content.map(messageFragment => {renderContent(messageFragment)})}
{/* Follow-up Questions Section */} {message.follow_up_questions && message.follow_up_questions.length > 0 && (

Follow-up Questions:

{message.follow_up_questions.map((question, idx) => ( ))}
)}
); }; // Export the observer-wrapped component to allow MobX to react to state changes export default observer(MessageComponentBox);