import 'dart:async'; import 'package:flutter/foundation.dart'; import '../models/message.dart'; import '../services/chat_service.dart'; import '../providers/auth_provider.dart'; /// Chat provider for v3 API (dynamic Claude sessions) class ChatProvider with ChangeNotifier { final ChatService _chatService = ChatService(); final List _messages = []; static const int _maxMessages = 500; AuthProvider? _authProvider; StreamSubscription? _messageSubscription; StreamSubscription? _stateSubscription; StreamSubscription? _errorSubscription; List _sessions = []; ChatSession? _currentSession; bool _isProcessing = false; String? _error; List get messages => List.unmodifiable(_messages); List get sessions => List.unmodifiable(_sessions); ChatSession? get currentSession => _currentSession; bool get isProcessing => _isProcessing; bool get isConnected => _chatService.currentState == ChatConnectionState.connected; bool get isSessionConnected => _currentSession != null; ChatConnectionState get connectionState => _chatService.currentState; String? get error => _error; void updateAuth(AuthProvider auth) { _authProvider = auth; _chatService.setToken(auth.token); if (auth.isAuthenticated && !isConnected) { connect(); } else if (!auth.isAuthenticated && isConnected) { disconnect(); } } Future connect() async { if (_authProvider?.token == null) return; _chatService.setToken(_authProvider!.token); _stateSubscription?.cancel(); _stateSubscription = _chatService.connectionState.listen((state) { notifyListeners(); }); _errorSubscription?.cancel(); _errorSubscription = _chatService.errors.listen((error) { _error = error; notifyListeners(); }); _messageSubscription?.cancel(); _messageSubscription = _chatService.messages.listen(_handleMessage); await _chatService.connect(); } void _handleMessage(Map data) { final type = data['type']; debugPrint('ChatProvider: type=$type'); switch (type) { // Initial state with sessions case 'init': final sessionsList = data['sessions'] as List?; if (sessionsList != null) { _sessions = sessionsList .map((s) => ChatSession.fromJson(s)) .toList(); } notifyListeners(); break; // Sessions list update case 'sessions_list': final sessionsList = data['sessions'] as List?; if (sessionsList != null) { _sessions = sessionsList .map((s) => ChatSession.fromJson(s)) .toList(); } notifyListeners(); break; // Session created (v3) case 'session_created': final sessionId = data['session_id'] as String?; final name = data['name'] as String?; if (sessionId != null) { final newSession = ChatSession( sessionId: sessionId, name: name ?? 'New Session', ); _sessions.add(newSession); _messages.add(Message( role: 'system', content: 'Sesión creada: ${newSession.name}', )); // Auto-connect to new session connectToSession(sessionId); } notifyListeners(); break; // Connected to session (v3) case 'session_connected': final sessionId = data['session_id'] as String?; final name = data['name'] as String?; if (sessionId != null) { _currentSession = _sessions.firstWhere( (s) => s.sessionId == sessionId, orElse: () => ChatSession( sessionId: sessionId, name: name ?? 'Session', ), ); _messages.clear(); _messages.add(Message( role: 'system', content: 'Conectado a: ${_currentSession!.name}', )); } notifyListeners(); break; // Output from Claude (v3) case 'output': final content = data['content'] as String? ?? ''; if (content.isEmpty) break; debugPrint('ChatProvider: OUTPUT "${content.substring(0, content.length > 50 ? 50 : content.length)}"'); _isProcessing = true; // Check if it's a progress indicator if (content.startsWith('procesando')) { // Update or create progress message if (_messages.isNotEmpty && _messages.last.role == 'assistant' && _messages.last.isStreaming == true) { final lastIndex = _messages.length - 1; _messages[lastIndex] = Message( role: 'assistant', content: content, isStreaming: true, ); } else { _messages.add(Message( role: 'assistant', content: content, isStreaming: true, )); } } else { // Real content - replace progress or add new if (_messages.isNotEmpty && _messages.last.role == 'assistant' && _messages.last.isStreaming == true) { final lastIndex = _messages.length - 1; final lastContent = _messages[lastIndex].content; // If last message was progress, replace it. Otherwise append. if (lastContent.startsWith('procesando')) { _messages[lastIndex] = Message( role: 'assistant', content: content, isStreaming: true, ); } else { // Append to existing response _messages[lastIndex] = Message( role: 'assistant', content: lastContent + content, isStreaming: true, ); } } else { _messages.add(Message( role: 'assistant', content: content, isStreaming: true, )); } } _trimMessages(); notifyListeners(); break; // Response complete (v3) case 'done': _isProcessing = false; // Mark last assistant message as complete if (_messages.isNotEmpty && _messages.last.role == 'assistant') { final lastIndex = _messages.length - 1; _messages[lastIndex] = Message( role: 'assistant', content: _messages[lastIndex].content, isStreaming: false, ); } notifyListeners(); break; case 'error': _isProcessing = false; final errorMsg = data['message'] ?? data['content'] ?? 'Error'; if (errorMsg.toString().isNotEmpty) { _error = errorMsg; _messages.add(Message( role: 'system', content: 'Error: $errorMsg', )); } notifyListeners(); break; } } void _trimMessages() { while (_messages.length > _maxMessages) { _messages.removeAt(0); } } /// Create and connect to a new session void createSession(String name) { _chatService.createSession(name); } /// Connect to an existing session void connectToSession(String sessionId) { _chatService.connectToSession(sessionId); } /// Refresh sessions list void refreshSessions() { _chatService.listSessions(); } /// Send message to current session void sendMessage(String content) { if (content.trim().isEmpty) return; if (!isConnected) { _error = 'No conectado al servidor'; notifyListeners(); return; } if (_currentSession == null) { _error = 'No hay sesión activa'; notifyListeners(); return; } if (_isProcessing) { _error = 'Espera a que termine la respuesta anterior'; notifyListeners(); return; } _messages.add(Message( role: 'user', content: content, )); _trimMessages(); _isProcessing = true; notifyListeners(); _chatService.sendMessage(content); } void clearMessages() { _messages.clear(); notifyListeners(); } void clearError() { _error = null; notifyListeners(); } void disconnect() { _chatService.disconnect(); _messageSubscription?.cancel(); _stateSubscription?.cancel(); _errorSubscription?.cancel(); _currentSession = null; } @override void dispose() { disconnect(); _chatService.dispose(); super.dispose(); } }