aboutsummaryrefslogtreecommitdiff
path: root/src/client/views/nodes/chatbot/chatboxcomponents/MessageComponent.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'src/client/views/nodes/chatbot/chatboxcomponents/MessageComponent.tsx')
-rw-r--r--src/client/views/nodes/chatbot/chatboxcomponents/MessageComponent.tsx146
1 files changed, 146 insertions, 0 deletions
diff --git a/src/client/views/nodes/chatbot/chatboxcomponents/MessageComponent.tsx b/src/client/views/nodes/chatbot/chatboxcomponents/MessageComponent.tsx
new file mode 100644
index 000000000..801becb64
--- /dev/null
+++ b/src/client/views/nodes/chatbot/chatboxcomponents/MessageComponent.tsx
@@ -0,0 +1,146 @@
+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';
+
+/**
+ * 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;
+ index: number;
+ 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<MessageComponentProps> = ({ message, index, onFollowUpClick, onCitationClick, updateMessageCitations }) => {
+ // 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 (
+ <span key={i} className="grounded-text">
+ <ReactMarkdown>{item.text}</ReactMarkdown>
+ {citation_ids.map((id, idx) => {
+ const citation = message.citations?.find(c => c.citation_id === id);
+ if (!citation) return null;
+ return (
+ <button key={i + idx} className="citation-button" onClick={() => onCitationClick(citation)}>
+ {i + 1}
+ </button>
+ );
+ })}
+ </span>
+ );
+ }
+
+ // Handle normal text
+ else if (item.type === TEXT_TYPE.NORMAL) {
+ return (
+ <span key={i} className="normal-text">
+ <ReactMarkdown>{item.text}</ReactMarkdown>
+ </span>
+ );
+ }
+
+ // Handle query type content
+ else if ('query' in item) {
+ return (
+ <span key={i} className="query-text">
+ <ReactMarkdown>{JSON.stringify(item.query)}</ReactMarkdown>
+ </span>
+ );
+ }
+
+ // Fallback for any other content type
+ else {
+ return (
+ <span key={i}>
+ <ReactMarkdown>{JSON.stringify(item)}</ReactMarkdown>
+ </span>
+ );
+ }
+ };
+
+ // 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 (
+ <div key={info.index} className="dropdown-item">
+ <strong>Thought:</strong> {info.content}
+ </div>
+ );
+ } else if (info.type === PROCESSING_TYPE.ACTION) {
+ return (
+ <div key={info.index} className="dropdown-item">
+ <strong>Action:</strong> {info.content}
+ </div>
+ );
+ }
+ return null;
+ };
+
+ return (
+ <div className={`message ${message.role}`}>
+ {/* Processing Information Dropdown */}
+ {hasProcessingInfo && (
+ <div className="processing-info">
+ <button className="toggle-info" onClick={() => setDropdownOpen(!dropdownOpen)}>
+ {dropdownOpen ? 'Hide Agent Thoughts/Actions' : 'Show Agent Thoughts/Actions'}
+ </button>
+ {dropdownOpen && <div className="info-content">{message.processing_info.map(renderProcessingInfo)}</div>}
+ <br />
+ </div>
+ )}
+
+ {/* Message Content */}
+ <div className="message-content">{message.content && message.content.map(messageFragment => <React.Fragment key={messageFragment.index}>{renderContent(messageFragment)}</React.Fragment>)}</div>
+
+ {/* Follow-up Questions Section */}
+ {message.follow_up_questions && message.follow_up_questions.length > 0 && (
+ <div className="follow-up-questions">
+ <h4>Follow-up Questions:</h4>
+ <div className="questions-list">
+ {message.follow_up_questions.map((question, idx) => (
+ <button key={idx} className="follow-up-button" onClick={() => onFollowUpClick(question)}>
+ {question}
+ </button>
+ ))}
+ </div>
+ </div>
+ )}
+ </div>
+ );
+};
+
+// Export the observer-wrapped component to allow MobX to react to state changes
+export default observer(MessageComponentBox);