import 'dart:async'; import 'package:flutter/foundation.dart'; import '../models/chat.dart'; import '../models/message.dart'; import '../services/chat_service.dart'; class ChatProvider with ChangeNotifier { final ChatService _chatService = ChatService(); List _chats = []; Chat? _currentChat; bool _isLoading = false; bool _isSendingMessage = false; String? _errorMessage; Timer? _pollingTimer; List get chats => _chats; Chat? get currentChat => _currentChat; bool get isLoading => _isLoading; bool get isSendingMessage => _isSendingMessage; String? get errorMessage => _errorMessage; /// Fetch list of chats Future fetchChats({ required String accessToken, int page = 1, int perPage = 25, }) async { _isLoading = true; _errorMessage = null; notifyListeners(); try { final result = await _chatService.getChats( accessToken: accessToken, page: page, perPage: perPage, ); if (result['success'] == true) { _chats = result['chats'] as List; _errorMessage = null; } else { _errorMessage = result['error'] ?? 'Failed to fetch chats'; } } catch (e) { _errorMessage = 'Error: ${e.toString()}'; } finally { _isLoading = false; notifyListeners(); } } /// Fetch a specific chat with messages Future fetchChat({ required String accessToken, required String chatId, }) async { _isLoading = true; _errorMessage = null; notifyListeners(); try { final result = await _chatService.getChat( accessToken: accessToken, chatId: chatId, ); if (result['success'] == true) { _currentChat = result['chat'] as Chat; _errorMessage = null; } else { _errorMessage = result['error'] ?? 'Failed to fetch chat'; } } catch (e) { _errorMessage = 'Error: ${e.toString()}'; } finally { _isLoading = false; notifyListeners(); } } /// Create a new chat Future createChat({ required String accessToken, String? title, String? initialMessage, String model = 'gpt-4', }) async { _isLoading = true; _errorMessage = null; notifyListeners(); try { final result = await _chatService.createChat( accessToken: accessToken, title: title, initialMessage: initialMessage, model: model, ); if (result['success'] == true) { final chat = result['chat'] as Chat; _currentChat = chat; _chats.insert(0, chat); _errorMessage = null; // Start polling for AI response if initial message was sent if (initialMessage != null) { _startPolling(accessToken, chat.id); } _isLoading = false; notifyListeners(); return chat; } else { _errorMessage = result['error'] ?? 'Failed to create chat'; _isLoading = false; notifyListeners(); return null; } } catch (e) { _errorMessage = 'Error: ${e.toString()}'; _isLoading = false; notifyListeners(); return null; } } /// Send a message to the current chat Future sendMessage({ required String accessToken, required String chatId, required String content, }) async { _isSendingMessage = true; _errorMessage = null; notifyListeners(); try { final result = await _chatService.sendMessage( accessToken: accessToken, chatId: chatId, content: content, ); if (result['success'] == true) { final message = result['message'] as Message; // Add the message to current chat if it's loaded if (_currentChat != null && _currentChat!.id == chatId) { _currentChat = _currentChat!.copyWith( messages: [..._currentChat!.messages, message], ); } _errorMessage = null; // Start polling for AI response _startPolling(accessToken, chatId); } else { _errorMessage = result['error'] ?? 'Failed to send message'; } } catch (e) { _errorMessage = 'Error: ${e.toString()}'; } finally { _isSendingMessage = false; notifyListeners(); } } /// Update chat title Future updateChatTitle({ required String accessToken, required String chatId, required String title, }) async { try { final result = await _chatService.updateChat( accessToken: accessToken, chatId: chatId, title: title, ); if (result['success'] == true) { final updatedChat = result['chat'] as Chat; // Update in the list final index = _chats.indexWhere((c) => c.id == chatId); if (index != -1) { _chats[index] = updatedChat; } // Update current chat if it's the same if (_currentChat != null && _currentChat!.id == chatId) { _currentChat = updatedChat; } notifyListeners(); } } catch (e) { _errorMessage = 'Error: ${e.toString()}'; notifyListeners(); } } /// Delete a chat Future deleteChat({ required String accessToken, required String chatId, }) async { try { final result = await _chatService.deleteChat( accessToken: accessToken, chatId: chatId, ); if (result['success'] == true) { _chats.removeWhere((c) => c.id == chatId); if (_currentChat != null && _currentChat!.id == chatId) { _currentChat = null; } notifyListeners(); return true; } else { _errorMessage = result['error'] ?? 'Failed to delete chat'; notifyListeners(); return false; } } catch (e) { _errorMessage = 'Error: ${e.toString()}'; notifyListeners(); return false; } } /// Start polling for new messages (AI responses) void _startPolling(String accessToken, String chatId) { _stopPolling(); _pollingTimer = Timer.periodic(const Duration(seconds: 2), (timer) async { await _pollForUpdates(accessToken, chatId); }); } /// Stop polling void _stopPolling() { _pollingTimer?.cancel(); _pollingTimer = null; } /// Poll for updates Future _pollForUpdates(String accessToken, String chatId) async { try { final result = await _chatService.getChat( accessToken: accessToken, chatId: chatId, ); if (result['success'] == true) { final updatedChat = result['chat'] as Chat; // Check if we have new messages if (_currentChat != null && _currentChat!.id == chatId) { final oldMessageCount = _currentChat!.messages.length; final newMessageCount = updatedChat.messages.length; if (newMessageCount > oldMessageCount) { _currentChat = updatedChat; notifyListeners(); // Check if the last message is from assistant and complete final lastMessage = updatedChat.messages.lastOrNull; if (lastMessage != null && lastMessage.isAssistant) { // Stop polling after getting assistant response _stopPolling(); } } } } } catch (e) { // Silently fail polling errors to avoid interrupting user experience debugPrint('Polling error: ${e.toString()}'); } } /// Clear current chat void clearCurrentChat() { _currentChat = null; _stopPolling(); notifyListeners(); } @override void dispose() { _stopPolling(); super.dispose(); } }