addMessage method

void addMessage(
  1. ChatMessage message
)

Adds a new message to the chat.

Implementation

void addMessage(ChatMessage message) {
  final messageId = _getMessageId(message);
  if (!_messageCache.containsKey(messageId)) {
    // Determine if this is a user message using the ID and properties
    final isFromUser = ((message.customProperties?['isUserMessage'] as bool?) == true) ||
        (message.customProperties?['source'] == 'user') ||
        (message.user.id != 'ai' && message.user.id != 'bot' && message.user.id != 'assistant');

    // Get the user ID for response tracking
    final userId = message.user.id;

    // Get responseId if available - for linking multiple messages as one response
    final responseId = message.customProperties?['responseId'] as String?;

    // Track if this is the start of a response (user changed and it's not a user message)
    final isStartOfResponse = (_lastMessageUserId != userId && !isFromUser) ||
        message.customProperties?['isStartOfResponse'] == true;

    _lastMessageUserId = userId;

    // Create a property map to track messaging state
    final updatedProperties = <String, dynamic>{...?message.customProperties};

    // Track the first message of an AI response
    if (isStartOfResponse) {
      _currentResponseFirstMessageId = messageId;
      // Use properties to mark this as the first message of a response
      updatedProperties['isFirstResponseMessage'] = true;
      updatedProperties['isStartOfResponse'] = true;

      debugPrint(
          'NEW RESPONSE: First message ID: $messageId from user: $userId responseId: $responseId');
    }

    // When a user message appears, we need to reset the first response tracking
    if (isFromUser) {
      // Clear the first response message ID whenever a user sends a message
      // This way, the next AI message will become the first of a new response
      _currentResponseFirstMessageId = null;
      debugPrint('USER MESSAGE: Reset response tracking for user: $userId');
    }

    // For related messages with the same responseId, we want to keep the first message
    // of the chain as the scroll target
    if (responseId != null && !isStartOfResponse && !isFromUser) {
      // Check if we already have a message with this responseId flagged as start of response
      final existingFirstMessage = _messages.firstWhere(
        (msg) =>
            msg.customProperties?['responseId'] == responseId &&
            (msg.customProperties?['isStartOfResponse'] == true ||
                msg.customProperties?['isFirstResponseMessage'] == true),
        orElse: () => message,
      );

      // If we found a message that's marked as first in this response chain,
      // use its ID for scrolling to ensure consistent behavior
      if (existingFirstMessage != message &&
          existingFirstMessage.customProperties?['responseId'] == responseId) {
        final existingFirstId = _getMessageId(existingFirstMessage);
        _currentResponseFirstMessageId = existingFirstId;
        debugPrint(
            'CHAIN MESSAGE: Using existing first message ID: $existingFirstId for responseId: $responseId');
      }
    }

    // Mark user messages for identification
    if (!updatedProperties.containsKey('isUserMessage') &&
        !updatedProperties.containsKey('source')) {
      updatedProperties['isUserMessage'] = isFromUser;
      debugPrint('MESSAGE TYPE: userId=${message.user.id}, isUserMessage=$isFromUser');
    }

    // Create a copy of the message with updated properties
    final updatedMessage = message.copyWith(customProperties: updatedProperties);

    if (paginationConfig.reverseOrder) {
      // In reverse order (newest first), new messages go at the beginning (index 0)
      // With ListView.builder(reverse: true), this puts newest messages at the bottom
      _messages.insert(0, updatedMessage);
    } else {
      // In chronological order (oldest first), new messages go at the end
      // With ListView.builder(reverse: false), this puts newest messages at the bottom
      _messages.add(updatedMessage);
    }
    _messageCache[messageId] = updatedMessage;
    notifyListeners();

    // Determine if we should scroll based on the configuration
    final config = scrollBehaviorConfig;

    // Detect if this is a user message
    final isUserMessage =
        updatedProperties['isUserMessage'] as bool? ?? updatedProperties['source'] == 'user';

    // Identify the first message in a response chain
    final isFirstResponse = updatedProperties['isFirstResponseMessage'] as bool? ?? false;

    final shouldScroll = _determineShouldScroll(config, isUserMessage, isFirstResponse);

    if (shouldScroll) {
      debugPrint('SCROLLING: After render for isUserMessage=$isUserMessage');
      _scrollAfterRender(isUserMessage, isStartOfResponse, config);
    } else {
      debugPrint('NOT SCROLLING: Message doesn\'t meet scroll criteria');
    }
  }
}