296 lines
8.3 KiB
Dart
296 lines
8.3 KiB
Dart
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<Message> _messages = [];
|
|
static const int _maxMessages = 500;
|
|
|
|
AuthProvider? _authProvider;
|
|
StreamSubscription? _messageSubscription;
|
|
StreamSubscription? _stateSubscription;
|
|
StreamSubscription? _errorSubscription;
|
|
|
|
List<ChatSession> _sessions = [];
|
|
ChatSession? _currentSession;
|
|
bool _isProcessing = false;
|
|
String? _error;
|
|
|
|
List<Message> get messages => List.unmodifiable(_messages);
|
|
List<ChatSession> 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<void> 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<String, dynamic> data) {
|
|
final type = data['type'];
|
|
debugPrint('ChatProvider: type=$type');
|
|
|
|
switch (type) {
|
|
// Initial state with sessions
|
|
case 'init':
|
|
final sessionsList = data['sessions'] as List<dynamic>?;
|
|
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<dynamic>?;
|
|
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();
|
|
}
|
|
}
|