ChatMessageItem Component
The ChatMessageItem component handles the display and interaction for individual chat messages, providing comprehensive functionality including export capabilities, feedback collection, thinking process visualization, and responsive layout management. It serves as the primary interface for user interaction with individual messages in the chat.
Overview
ChatMessageItem provides rich message interaction capabilities including:
- Multi-format export functionality (PDF, Word, PowerPoint, HTML)
- Thinking process visualization for AI reasoning transparency
- User feedback collection through Langfuse integration
- Responsive layout adapting to mobile and desktop
- Avatar integration with assistant linking
- Export link management and persistence
- Accessibility support with proper ARIA attributes
Architecture
Core Dependencies
import { Avatar, AvatarImage, AvatarFallback } from "@/components/ui/avatar";
import RelativeTime from '@/components/RelativeTime';
import MessageContent from '@/components/MessageContent';
import QuartoExportButton, { ExportFormat } from '@/components/QuartoExportButton';
import QuartoExportLinks, { ExportLinksManager, DownloadInfo } from '@/components/QuartoExportLinks';
import { MessageFeedback } from '@/components/MessageFeedback';
import { MessageFeedbackDialog } from '@/components/MessageFeedbackDialog';
import { MessageProvider } from '@/contexts/MessageContext';
import { extractThinkingContent } from '@/utils/thinkingExtractor';
Component Structure
ChatMessageItem
├── Avatar & Metadata Row
│ ├── Avatar (clickable for assistant links)
│ ├── Sender Name
│ └── Timestamp
├── Message Content Box
│ └── MessageContent (with streaming support)
├── Action Buttons Row (non-user messages only)
│ ├── MessageFeedback (Langfuse integration)
│ ├── MessageFeedbackDialog
│ ├── Thinking Toggle
│ └── Export Toggle
├── Thinking Content Panel (conditional)
│ └── Thinking Process Display
├── Export Panel (conditional)
│ ├── Format Selection
│ └── QuartoExportButton
└── Persistent Export Links
└── QuartoExportLinks
Props Interface
export interface ChatMessageItemProps {
// Core Message Data
message: ChatMessage;
isUserMessage: boolean;
// Display Configuration
avatar: string;
name: string;
initials: string;
// Functionality Control
enableExport?: boolean;
currentUser: User;
assistantId: string;
// Streaming State
isCurrentlyStreaming?: boolean;
thinkingContent?: string;
isLastMessage?: boolean;
// Tracking & Analytics
traceId?: string;
}
Core Functionality
1. Message Export System
The component provides comprehensive export functionality with multiple format support:
// From ChatMessageItem.tsx:48-74
const [isExportOpen, setIsExportOpen] = useState(false);
const [selectedFormat, setSelectedFormat] = useState<ExportFormat>('docx');
const canExport = enableExport &&
!isUserMessage &&
message.content &&
message.content.trim() !== '';
const messageId = message.id || `msg-${btoa(message.content.substring(0, 50)).replace(/=/g, '')}-${message.timestamp}`;
const handleFormatChange = (format: ExportFormat) => {
setSelectedFormat(format);
};
const handleExportComplete = (msgId: string, downloadInfo: DownloadInfo) => {
ExportLinksManager.addExport(msgId, downloadInfo);
};
Export Features:
- Multiple Formats: PDF, Word (DOCX), PowerPoint (PPTX), HTML
- Content Integration: Includes thinking content when visible
- Metadata Enrichment: Comprehensive document metadata
- Link Management: Persistent export links for re-download
- User Context: Current user information in exports
2. Thinking Process Visualization
// From ChatMessageItem.tsx:89-104
const { cleanContent, extractedThinking } = useMemo(() => {
// If streaming is active, use the provided thinking content
if (isCurrentlyStreaming) {
return { cleanContent: message.content, extractedThinking: thinkingContent };
}
// Otherwise try to extract thinking content from the message content
return extractThinkingContent(message.content);
}, [message.content, thinkingContent, isCurrentlyStreaming]);
const effectiveThinkingContent = thinkingContent || extractedThinking;
const hasThinkingContent = Boolean(effectiveThinkingContent && effectiveThinkingContent.trim() !== '');
Thinking Features:
- Dynamic Extraction: Extracts thinking content from message or uses provided content
- Streaming Support: Handles live thinking content during streaming
- Toggle Visibility: User can show/hide thinking process
- Visual Distinction: Special styling for thinking content display
3. User Feedback Integration
// From ChatMessageItem.tsx:156-169
{traceId && (
<>
<MessageFeedback
traceId={traceId}
messageId={message.id}
className="opacity-0 group-hover:opacity-100 transition-opacity duration-200"
/>
<MessageFeedbackDialog
traceId={traceId}
messageId={message.id}
className="opacity-0 group-hover:opacity-100 transition-opacity duration-200"
/>
</>
)}
Feedback Features:
- Langfuse Integration: Direct feedback to tracing system
- Hover Revelation: Feedback buttons appear on message hover
- Dual Interface: Quick thumbs up/down and detailed feedback dialog
- Trace Linking: Associates feedback with specific AI responses
User Interface Design
Responsive Layout
// From ChatMessageItem.tsx:107-150
<div className="mb-4 last:mb-0 group">
<div className={`flex flex-col ${isUserMessage ? 'md:items-end' : 'md:items-start'} items-stretch`}>
{/* Avatar and Metadata Row */}
<div className={`flex items-center gap-2 mb-1 ${isUserMessage ? 'flex-row-reverse' : 'flex-row'}`}>
{/* Avatar, name, timestamp */}
</div>
{/* Message Content Box */}
<div className={`max-w-full md:max-w-[85%] overflow-hidden ${isUserMessage ? 'md:ml-auto' : 'md:mr-auto'} min-w-0`}>
<MessageProvider isLastMessage={isLastMessage}>
<MessageContent
content={cleanContent}
isUser={isUserMessage}
role={message.sender}
streaming={true}
streamSpeed={50}
isCurrentlyStreaming={isCurrentlyStreaming}
/>
</MessageProvider>
</div>
</div>
</div>
Layout Features:
- Mobile First: Full width on mobile, constrained on desktop
- Alignment Logic: User messages right-aligned, assistant messages left-aligned
- Responsive Constraints: 85% max width on desktop for readability
- Avatar Integration: Clickable avatars for assistant navigation
Action Buttons with Hover States
// From ChatMessageItem.tsx:171-203
{hasThinkingContent && (
<button
onClick={toggleThinking}
className="inline-flex items-center justify-center gap-1 whitespace-nowrap font-medium ring-offset-background transition-all duration-200 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 hover:bg-accent rounded-md h-6 px-2 text-xs hover:text-foreground opacity-0 group-hover:opacity-100"
style={{color: showThinking ? 'transparent' : 'var(--muted-foreground)',
background: showThinking ?
'linear-gradient(90deg, var(--primary) 0%, var(--primary-lighter, #7c78ff) 100%)' :
'transparent',
backgroundClip: showThinking ? 'text' : 'border-box',
WebkitBackgroundClip: showThinking ? 'text' : 'border-box'
}}
aria-expanded={showThinking}
aria-controls={`thinking-panel-${messageId}`}
aria-label="Toggle thinking process"
>
<Brain className="h-3.5 w-3.5 mr-0.5" />
{showThinking ? "Hide thinking" : "Show thinking"}
{showThinking ? <ChevronUp /> : <ChevronDown />}
</button>
)}
Action Button Features:
- Hover Revelation: Buttons appear on message hover
- Visual State Indication: Active states show gradient styling
- Accessibility: Proper ARIA attributes for screen readers
- Smooth Transitions: 200ms opacity transitions for polished UX
Export Format Selection
// From ChatMessageItem.tsx:260-299
<div className="flex border border-input rounded-md overflow-hidden shadow-sm">
{/* PDF Button */}
<button
onClick={() => handleFormatChange('pdf')}
className={`px-3 py-1 text-sm transition-colors ${selectedFormat === 'pdf' ? 'bg-primary text-primary-foreground' : 'bg-background text-foreground hover:bg-accent'}`}
aria-pressed={selectedFormat === 'pdf'}
>
PDF
</button>
{/* Word (DOCX) Button */}
<button
onClick={() => handleFormatChange('docx')}
className={`px-3 py-1 text-sm transition-colors border-l border-input ${selectedFormat === 'docx' ? 'bg-primary text-primary-foreground' : 'bg-background text-foreground hover:bg-accent'}`}
aria-pressed={selectedFormat === 'docx'}
>
Word
</button>
{/* PowerPoint and HTML buttons follow same pattern */}
</div>
Format Selection Features:
- Visual Selection State: Selected format highlighted with primary color
- Segmented Control: Connected buttons forming a cohesive control
- Accessibility: ARIA pressed states for selection indication
- Comprehensive Formats: PDF, Word, PowerPoint, HTML support
Thinking Content Display
// From ChatMessageItem.tsx:226-251
{hasThinkingContent && showThinking && (
<div
id={`thinking-panel-${messageId}`}
className={`mt-2 w-full ${isUserMessage ? 'ml-auto' : 'mr-auto'} max-w-[85%]`}
>
<div className="opacity-90">
<div className="prose prose-sm max-w-none rounded-lg border p-3 mt-1 bg-gradient-to-br from-slate-50 to-indigo-50 text-xs overflow-x-auto shadow-sm">
<div className="text-xs font-medium flex items-center mb-2" style={{color: 'var(--primary)' }}>
<Brain className="h-3.5 w-3.5 mr-1.5" />
<span>Thinking process:</span>
</div>
<MessageProvider isLastMessage={isLastMessage}>
<MessageContent
content={effectiveThinkingContent}
isUser={false}
role="assistant"
className="text-xs"
streaming={false}
/>
</MessageProvider>
</div>
</div>
</div>
)}
Thinking Display Features:
- Visual Distinction: Gradient background and smaller text size
- Brain Icon: Clear visual indicator of thinking content
- No Streaming: Thinking content displays immediately, not streamed
- Proper Containment: Respects message width constraints
Export Integration
Comprehensive Metadata
// From ChatMessageItem.tsx:314-341
reportData={{
// Message and conversation metadata
timestamp: message.timestamp || Date.now(),
messageId: messageId,
senderName: name,
userId: currentUser?.uid,
// Document metadata
title: `Chat Export - ${new Date(message.timestamp || Date.now()).toLocaleString()}`,
author: currentUser?.displayName || currentUser?.email || 'User',
authorEmail: currentUser?.email || '',
// System metadata
exportDate: new Date().toISOString(),
exportTimestamp: Date.now(),
sourceApplication: 'AI Chat Interface',
// URL information
chatUrl: typeof window !== 'undefined' ? window.location.href : '',
assistantContext: `Assistant ID: ${assistantId}`,
// Format-specific metadata
format: selectedFormat,
formatVersion: '1.0',
// Include thinking status
includesThinking: hasThinkingContent && showThinking
}}
Metadata Features:
- User Context: Current user information and authentication
- Document Context: Title, creation date, source application
- Technical Context: URLs, assistant IDs, format specifications
- Content Context: Whether thinking content is included
Export Link Management
// From ChatMessageItem.tsx:363-368
{canExport && (
<div className={`w-full ${isUserMessage ? 'ml-auto' : 'mr-auto'} max-w-[85%]`}>
<QuartoExportLinks messageId={messageId} className="mt-2" />
</div>
)}
Link Management Features:
- Persistent Storage: Export links remain available after generation
- Re-download Support: Users can download exports multiple times
- Format Organization: Links organized by export format
- Automatic Cleanup: Old links can be automatically pruned
Performance Optimizations
Component Memoization
// From ChatMessageItem.tsx:34
const ChatMessageItem = React.memo(function ChatMessageItem({
// Component implementation
});
Content Processing Memoization
// From ChatMessageItem.tsx:89-98
const { cleanContent, extractedThinking } = useMemo(() => {
if (isCurrentlyStreaming) {
return { cleanContent: message.content, extractedThinking: thinkingContent };
}
return extractThinkingContent(message.content);
}, [message.content, thinkingContent, isCurrentlyStreaming]);
Performance Features:
- React.memo: Prevents unnecessary re-renders
- Content Memoization: Expensive content processing cached
- Selective Dependencies: Only re-processes when necessary inputs change
Context Integration
Message Provider Integration
// From ChatMessageItem.tsx:140-149
<MessageProvider isLastMessage={isLastMessage}>
<MessageContent
content={cleanContent}
isUser={isUserMessage}
role={message.sender}
streaming={true}
streamSpeed={50}
isCurrentlyStreaming={isCurrentlyStreaming}
/>
</MessageProvider>
Context Features:
- Message Context: Provides message-specific context to children
- Last Message Detection: Special handling for final message in conversation
- Streaming Coordination: Proper streaming state management
Accessibility Features
ARIA Support
// Button accessibility
aria-expanded={showThinking}
aria-controls={`thinking-panel-${messageId}`}
aria-label="Toggle thinking process"
aria-pressed={selectedFormat === 'pdf'}
Keyboard Navigation
- Focus Management: Proper tab order through interactive elements
- Keyboard Activation: All buttons support keyboard activation
- Screen Reader Support: Descriptive labels and state information
Usage Examples
Basic Message Display
import ChatMessageItem from '@/components/ChatMessageItem';
function MyChat() {
const message = {
id: 'msg-123',
content: 'Hello, how can I help you?',
sender: 'assistant',
timestamp: Date.now()
};
return (
<ChatMessageItem
message={message}
isUserMessage={false}
avatar="/assistant-avatar.png"
name="AI Assistant"
initials="AI"
currentUser={currentUser}
assistantId="assistant-123"
enableExport={true}
traceId="trace-456"
/>
);
}
With Thinking Content
<ChatMessageItem
// ... basic props
thinkingContent="I need to analyze this request carefully..."
isCurrentlyStreaming={true}
isLastMessage={true}
/>
Export Disabled
<ChatMessageItem
// ... basic props
enableExport={false}
// No export panel will be shown
/>
Testing Considerations
Key Test Areas
- Export Functionality: Test all format exports and metadata inclusion
- Thinking Content: Test extraction, display, and toggle functionality
- Feedback Integration: Test Langfuse feedback submission
- Responsive Behavior: Test mobile vs desktop layout
- Avatar Navigation: Test assistant page navigation
- Accessibility: Test keyboard navigation and screen reader support
Mock Requirements
// Required mocks for testing
jest.mock('@/components/MessageContent');
jest.mock('@/components/QuartoExportButton');
jest.mock('@/components/QuartoExportLinks');
jest.mock('@/components/MessageFeedback');
jest.mock('@/components/MessageFeedbackDialog');
jest.mock('@/utils/thinkingExtractor');
jest.mock('next/link');
// Mock Firebase User
const mockUser = {
uid: 'user-123',
email: 'user@example.com',
displayName: 'Test User'
};
Example Test Structure
describe('ChatMessageItem', () => {
it('should display export panel when export is enabled', () => {
// Test export functionality
});
it('should toggle thinking content visibility', () => {
// Test thinking content toggle
});
it('should handle different message alignments', () => {
// Test responsive layout
});
it('should include thinking content in exports when visible', () => {
// Test export content inclusion
});
});
Error Handling
Export Error Handling
// From ChatMessageItem.tsx:348-352
onError={(error) => {
console.error('Export failed:', error);
// Potentially show a user-facing error message here
}}
Graceful Degradation
- Missing Exports: Component functions without export capability
- Missing Thinking Content: Functions without thinking display
- Missing Feedback: Works without Langfuse integration
- Avatar Failures: Falls back to initials when avatar unavailable
Future Enhancements
Potential Improvements
- Export Scheduling: Schedule exports for later processing
- Batch Export: Export multiple messages simultaneously
- Custom Templates: User-customizable export templates
- Collaboration Features: Share individual messages with others
- Rich Annotations: Add user notes and highlights to messages
The ChatMessageItem component provides a comprehensive, accessible, and feature-rich interface for individual message interaction, supporting advanced export capabilities, thinking process transparency, and user feedback collection while maintaining excellent performance and responsive design.