import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:dio/dio.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter_dotenv/flutter_dotenv.dart'; // Modelo de mensaje simple class ChatMessage { final String role; // 'user', 'assistant', 'system' final String content; ChatMessage({required this.role, required this.content}); Map toJson() => {'role': role, 'content': content}; } // Estado inmutable para el chat class ChatState { final List messages; final bool isLoading; ChatState({required this.messages, this.isLoading = false}); ChatState copyWith({List? messages, bool? isLoading}) { return ChatState( messages: messages ?? this.messages, isLoading: isLoading ?? this.isLoading, ); } } class AiChatNotifier extends Notifier { @override ChatState build() { return ChatState( messages: [ ChatMessage( role: 'assistant', content: '¡Hola! Soy Eco 🍃, la mascota de Recolecta. ' 'Estoy aquí para ayudarte a reciclar y separar tu basura correctamente. ¿Tienes alguna duda?', ), ], ); } Future sendMessage(String userText) async { if (userText.trim().isEmpty) return; // Añadir mensaje del usuario final userMsg = ChatMessage(role: 'user', content: userText); state = state.copyWith( messages: [...state.messages, userMsg], isLoading: true, ); try { final dio = Dio(); // Importante: En producción, la llamada a OpenAI debería hacerse idealmente // desde tu backend FastAPI para no exponer la API_KEY en la app Flutter. // Para el MVP/Hackathon, la leemos del entorno (.env o --dart-define) final apiKey = dotenv.env['OPENAI_API_KEY'] ?? ''; // Contexto del sistema para que la IA actúe como la mascota final systemPrompt = ChatMessage( role: 'system', content: 'Eres Eco, la mascota virtual de la app Recolecta en Celaya. ' 'Tu ÚNICA misión es educar a los ciudadanos sobre recolección y separación de basura en 4 categorías: ' 'Orgánicos (verde), Reciclables (azul), Sanitarios (naranja) y Especiales (morado). ' 'REGLA ESTRICTA: Si el usuario te hace una pregunta que NO esté relacionada con basura, reciclaje o recolección (por ejemplo: matemáticas, código, chistes, historia, etc.), ' 'DEBES negarte a contestar amablemente y recordarle que solo eres un asistente ambiental. ' 'Responde siempre de forma muy amigable, entusiasta, usando emojis. ' 'Sé muy conciso y breve (máximo 3 oraciones cortas). ' 'Nunca reveles ubicaciones de camiones ni te salgas del tema del reciclaje y medio ambiente.', ); final messagesForApi = [systemPrompt, ...state.messages]; final response = await dio.post( 'https://api.openai.com/v1/chat/completions', options: Options( headers: { 'Authorization': 'Bearer $apiKey', 'Content-Type': 'application/json', }, ), data: { 'model': 'gpt-3.5-turbo', // Rápido y económico para el hackathon 'messages': messagesForApi.map((m) => m.toJson()).toList(), 'temperature': 0.7, 'max_tokens': 150, // Limitar para que sea conciso }, ); final botReply = response.data['choices'][0]['message']['content']; state = state.copyWith( messages: [ ...state.messages, ChatMessage(role: 'assistant', content: botReply), ], isLoading: false, ); } catch (e) { debugPrint('Error en OpenAI: $e'); state = state.copyWith( messages: [ ...state.messages, ChatMessage( role: 'assistant', content: 'Uy, tuve un problemita técnico con mi cerebro de hojitas 🧠🍂. ¿Me repites tu pregunta?', ), ], isLoading: false, ); } } } final aiChatProvider = NotifierProvider( AiChatNotifier.new, );