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
// 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

  1. Export Functionality: Test all format exports and metadata inclusion
  2. Thinking Content: Test extraction, display, and toggle functionality
  3. Feedback Integration: Test Langfuse feedback submission
  4. Responsive Behavior: Test mobile vs desktop layout
  5. Avatar Navigation: Test assistant page navigation
  6. 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

  1. Export Scheduling: Schedule exports for later processing
  2. Batch Export: Export multiple messages simultaneously
  3. Custom Templates: User-customizable export templates
  4. Collaboration Features: Share individual messages with others
  5. 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.