ChatInput Component

The ChatInput component provides a comprehensive input interface for chat interactions, featuring message history management, template system, file browser integration, responsive behavior, and advanced interaction controls. It serves as the primary user input interface in the Aitana chat system.

Overview

ChatInput delivers a rich input experience including:

  • Message history persistence and recall
  • Template management for common responses
  • File browser integration for document handling
  • Responsive mobile/desktop behavior with auto-collapse
  • Vertex AI search widget integration
  • Dynamic UI form data integration
  • Comprehensive keyboard and accessibility support

Architecture

Core Dependencies

import { Button } from "@/components/ui/button";
import { Send, LogIn, Maximize2, Minimize2, Square } from 'lucide-react';
import { FileBrowserButton } from '@/components/FileBrowserButton';
import { HistoryTemplatesDrawer } from '@/components/HistoryTemplatesDrawer';
import { InputArea } from '@/components/InputArea';
import VertexSearchWidget from '@/components/VertexAISearchWidget';
import { useMessageState } from '@/contexts/MessageStateContext';

Component Structure

ChatInput
├── Collapsed View (mobile auto-collapse)
│   ├── Input Preview
│   └── Show Button
└── Expanded View
    ├── Tool Row
    │   ├── HistoryTemplatesDrawer
    │   ├── FileBrowserButton (conditional)
    │   └── VertexSearchWidget (conditional)
    ├── InputArea (main text input)
    └── Control Row
        ├── Expand/Collapse Toggle
        ├── Hide Button (mobile only)
        └── Send/Stop Button

Props Interface

interface ChatInputProps {
  // Basic Input Management
  input: string;
  onInputChange: (value: string) => void;
  onSendMessage: (messageContext: MessageContext) => void;
  
  // Assistant Context
  botName: string;
  senderName: string;
  userState: UserState;
  
  // State Management
  isStreaming: boolean;
  onLogin: () => void;
  onStopStreaming: () => void;
  
  // Tool Integration
  tools?: string[];
  toolConfigs?: Record<string, Record<string, any>>;
  
  // History & Templates
  messageHistoryLimit?: number;
  savedTemplates?: SavedTemplate[];
  onSaveTemplate?: (title: string, content: string) => void;
}

Core Functionality

1. Message History Management

The component implements comprehensive message history with localStorage persistence:

// From ChatInput.tsx:64-73
const [messageHistory, setMessageHistory] = useState<MessageHistoryItem[]>(() => {
  try {
    const savedHistory = localStorage.getItem('chatMessageHistory');
    return savedHistory ? JSON.parse(savedHistory) : [];
  } catch (error) {
    console.error('Failed to load message history:', error);
    return [];
  }
});

// From ChatInput.tsx:127-134
useEffect(() => {
  try {
    localStorage.setItem('chatMessageHistory', JSON.stringify(messageHistory));
  } catch (error) {
    console.error('Failed to save message history:', error);
  }
}, [messageHistory]);

Key Features:

  • Persistent Storage: Uses localStorage for cross-session persistence
  • Error Handling: Graceful fallback when localStorage is unavailable
  • History Limit: Configurable limit to prevent unbounded growth
  • Automatic Saving: Real-time persistence on changes

2. Template System

Supports both local and server-based template management:

// From ChatInput.tsx:75-89
const [localTemplates, setLocalTemplates] = useState<SavedTemplate[]>(() => {
  if (savedTemplates.length > 0) return []; // Don't initialize local templates if provided via props
  
  try {
    const savedLocalTemplates = localStorage.getItem('chatResponseTemplates');
    return savedLocalTemplates ? JSON.parse(savedLocalTemplates) : [];
  } catch (error) {
    console.error('Failed to load templates:', error);
    return [];
  }
});

// Template source selection
const templates = savedTemplates.length > 0 ? savedTemplates : localTemplates;
const canSaveToServer = savedTemplates.length > 0;

Template Management Features:

  • Dual Source Support: Local storage or server-provided templates
  • Template Creation: Save frequently used messages as templates
  • Template Application: One-click application to input field
  • Template Deletion: Remove unwanted templates (local only)

3. Responsive Behavior and Auto-Collapse

// From ChatInput.tsx:101-125
useEffect(() => {
  const checkMobile = () => {
    setIsMobile(window.innerWidth < 768); // Standard mobile breakpoint
  };
  
  checkMobile();
  window.addEventListener('resize', checkMobile);
  return () => window.removeEventListener('resize', checkMobile);
}, []);

// Auto-collapse when streaming starts on mobile
useEffect(() => {
  if (isStreaming && isMobile) {
    setIsCollapsed(true);
  } else if (!isStreaming && isCollapsed) {
    // Automatically expand when streaming ends
    setIsCollapsed(false);
  }
}, [isStreaming, isMobile]);

Responsive Features:

  • Mobile Detection: Automatic detection of mobile viewports
  • Auto-Collapse: Minimizes input during streaming on mobile
  • Smart Restoration: Automatically expands when streaming ends
  • Manual Controls: User can override auto-collapse behavior

4. Advanced Message Sending

// From ChatInput.tsx:147-183
const handleSend = () => {
  if (input.trim() || selectedItems.length > 0) {
    // Check for form data to prepend
    const formData = localStorage.getItem('dynamicui_form_data');
    let messageText = input;
    
    if (formData) {
      messageText = formData + input;
      // Clear the form data after using it
      localStorage.removeItem('dynamicui_form_data');
    }
    
    // Add to message history if there's text content
    if (input.trim()) {
      setMessageHistory(prev => {
        const newHistory = [...prev, { 
          text: input.trim(), 
          timestamp: new Date() 
        }];
        
        // Limit history size
        if (newHistory.length > messageHistoryLimit) {
          return newHistory.slice(-messageHistoryLimit);
        }
        return newHistory;
      });
    }
    
    onSendMessage({
      text: messageText,
      selectedItems: selectedItems
    });
    
    // Close drawer if open when sending
    setHistoryDrawerOpen(false);
  }
};

Advanced Features:

  • Dynamic UI Integration: Supports form data prepending from dynamic UI components
  • File Integration: Includes selected files in message context
  • History Tracking: Automatically adds sent messages to history
  • UI State Management: Closes history drawer on send

5. Tool Integration

File Browser Integration

// From ChatInput.tsx:96
const showFileBrowser = tools.includes('file-browser') || tools.includes('document_search_agent');

// From ChatInput.tsx:282-288
{showFileBrowser && (
  <FileBrowserButton
    onItemsSelected={setSelectedItems}
    selectedItems={selectedItems}
  />
)}

Vertex Search Integration

// From ChatInput.tsx:97-98
const showVertexSearch = tools.includes('vertex_search_app');
const vertexSearchConfig = toolConfigs.vertex_search_app || {};

// From ChatInput.tsx:290-296
{showVertexSearch && userState !== 'not-logged-in' && (
  <VertexSearchWidget
    configId={vertexSearchConfig.configId || "c09df02f-06e3-4285-b62f-823dcb95407d"}
    iconOnly={true}
  />
)}

Tool Features:

  • Conditional Display: Tools only appear when enabled in configuration
  • Context Integration: File selections integrated with message context
  • Configuration Support: Tools respect their specific configurations

6. Keyboard and Accessibility Support

// From ChatInput.tsx:185-190
const handleKeyDown = (e: React.KeyboardEvent<HTMLTextAreaElement>) => {
  if (e.key === 'Enter' && !e.shiftKey) {
    e.preventDefault();
    handleSend();
  }
};

Accessibility Features:

  • Keyboard Shortcuts: Enter to send, Shift+Enter for new line
  • Button Labels: Descriptive title attributes for all buttons
  • Screen Reader Support: Proper semantic structure and labeling

User Interface

Collapsed View (Mobile)

// From ChatInput.tsx:245-261
{isCollapsed ? (
  <div className="flex items-center justify-between px-4 py-2 h-12">
    <div className="text-sm text-gray-500 truncate flex-1">
      {input.trim() ? 
        (input.length > 30 ? `${input.substring(0, 30)}...` : input) : 
        `Send message to ${botName}...`}
    </div>
    <Button
      variant="ghost"
      size="sm"
      onClick={toggleCollapsed}
      className="ml-2"
    >
      Show
    </Button>
  </div>
) : (
  // Expanded view
)}

Features:

  • Content Preview: Shows truncated input content when collapsed
  • Space Efficient: Minimal height to maximize chat area
  • Easy Restoration: Single button to restore input area

Expanded View Layout

// From ChatInput.tsx:264-359
<div className="px-3 py-2">
  <div className="flex space-x-2 items-center">
    {/* Left side tools */}
    <div className="flex space-x-1">
      <HistoryTemplatesDrawer />
      {showFileBrowser && <FileBrowserButton />}
      {showVertexSearch && <VertexSearchWidget />}
    </div>

    {/* Chat input field */}
    <InputArea />
    
    {/* Right side buttons */}
    <div className="flex space-x-1">
      <Button onClick={toggleExpanded}>
        {isExpanded ? <Minimize2 /> : <Maximize2 />}
      </Button>
      
      {isMobile && (
        <Button onClick={toggleCollapsed}>
          <Minimize2 />
        </Button>
      )}
      
      {isStreaming ? (
        <Button onClick={onStopStreaming} variant="destructive">
          <Square />
        </Button>
      ) : (
        <Button 
          onClick={handleSend}
          disabled={(!input.trim() && selectedItems.length === 0)}
        >
          <Send />
        </Button>
      )}
    </div>
  </div>
</div>

Button States and Icons

// Send/Stop button logic
{isStreaming ? (
  <Button 
    onClick={onStopStreaming}
    variant="destructive"
    className="h-10 w-10"
    title="Stop response"
  >
    <Square className="h-4 w-4" /> 
  </Button>
) : (
  <Button 
    onClick={handleSend}
    disabled={(!input.trim() && selectedItems.length === 0)}
    className="h-10 w-10"
    title="Send message"
  >
    <Send className="h-4 w-4" />
  </Button>
)}

Button Features:

  • State-Aware Icons: Different icons for send vs stop states
  • Smart Enabling: Send button only enabled when content is available
  • Visual Feedback: Destructive styling for stop button

Context Integration

Message State Context

// From ChatInput.tsx:62-63
const { selectedItems, setSelectedItems } = useMessageState();

Integration Features:

  • File Selection State: Shares file selection state with other components
  • Context Coordination: Ensures consistency across component tree

Tool Configuration

// From ChatInput.tsx:55-56
tools = [],
toolConfigs = {},

Configuration Features:

  • Dynamic Tool Display: Tools appear based on configuration
  • Tool-Specific Settings: Each tool can have custom configuration
  • Graceful Defaults: Sensible defaults when configuration is missing

Storage and Persistence

Message History Persistence

// History structure
interface MessageHistoryItem {
  text: string;
  timestamp: Date;
}

// Storage key: 'chatMessageHistory'
// Automatic cleanup when history exceeds messageHistoryLimit

Template Persistence

// Template structure
interface SavedTemplate {
  id: string;
  title: string;
  content: string;
}

// Storage key: 'chatResponseTemplates'
// Only used when no server templates provided

Dynamic UI Integration

// Form data storage
// Storage key: 'dynamicui_form_data'
// Automatically cleared after use

Performance Optimizations

State Management

  • Lazy Loading: Templates and history loaded only on component mount
  • Efficient Updates: Uses functional state updates to prevent unnecessary re-renders
  • Conditional Rendering: Tools only render when enabled

Event Handling

// Memoized event handlers
const handleSend = useCallback(() => { /* ... */ }, [dependencies]);
const handleKeyDown = useCallback(() => { /* ... */ }, [dependencies]);

Usage Examples

Basic Chat Input

import { ChatInput } from '@/components/ChatInput';

function MyChat() {
  const [input, setInput] = useState('');
  const [isStreaming, setIsStreaming] = useState(false);

  return (
    <ChatInput
      input={input}
      botName="Assistant"
      senderName="User"
      userState="user"
      isStreaming={isStreaming}
      onInputChange={setInput}
      onSendMessage={handleSendMessage}
      onLogin={handleLogin}
      onStopStreaming={handleStopStreaming}
    />
  );
}

With Tool Integration

<ChatInput
  // ... basic props
  tools={['file-browser', 'vertex_search_app']}
  toolConfigs={{
    vertex_search_app: {
      configId: 'custom-config-id'
    }
  }}
/>

With Server Templates

<ChatInput
  // ... basic props
  savedTemplates={serverTemplates}
  onSaveTemplate={(title, content) => {
    saveTemplateToServer(title, content);
  }}
  messageHistoryLimit={100}
/>

Testing Considerations

Key Test Areas

  1. Message History: Test persistence and recall functionality
  2. Template System: Test local vs server template management
  3. Responsive Behavior: Test mobile auto-collapse functionality
  4. Tool Integration: Test conditional tool display and interaction
  5. Form Data Integration: Test dynamic UI form data handling
  6. Keyboard Shortcuts: Test Enter/Shift+Enter behavior

Mock Requirements

// Required mocks for testing
jest.mock('@/contexts/MessageStateContext');
jest.mock('@/components/FileBrowserButton');
jest.mock('@/components/HistoryTemplatesDrawer');
jest.mock('@/components/InputArea');
jest.mock('@/components/VertexAISearchWidget');

// Mock localStorage
const localStorageMock = {
  getItem: jest.fn(),
  setItem: jest.fn(),
  removeItem: jest.fn(),
  clear: jest.fn(),
};
global.localStorage = localStorageMock;

Example Test Structure

describe('ChatInput', () => {
  it('should persist message history to localStorage', () => {
    // Test history persistence
  });
  
  it('should auto-collapse on mobile when streaming', () => {
    // Test responsive behavior
  });
  
  it('should integrate dynamic UI form data', () => {
    // Test form data integration
  });
  
  it('should show tools based on configuration', () => {
    // Test tool integration
  });
});

Error Handling

Storage Errors

try {
  localStorage.setItem('chatMessageHistory', JSON.stringify(messageHistory));
} catch (error) {
  console.error('Failed to save message history:', error);
}

Graceful Degradation

  • Storage Failures: Component continues functioning without persistence
  • Tool Failures: Missing tools don’t break the input interface
  • Template Errors: Falls back to basic input when template system fails

Future Enhancements

Potential Improvements

  1. Voice Input: Speech-to-text integration
  2. Rich Text: Support for formatting in input area
  3. Drag & Drop: File drag-and-drop support
  4. Auto-Complete: Smart suggestions based on history
  5. Multi-Modal: Image and voice message support

The ChatInput component provides a comprehensive, accessible, and responsive input interface that scales from basic chat needs to advanced multi-modal interactions with tools, templates, and intelligent UI adaptations.