121 lines
3.8 KiB
Dart
121 lines
3.8 KiB
Dart
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<String, dynamic> toJson() => {'role': role, 'content': content};
|
|
}
|
|
|
|
// Estado inmutable para el chat
|
|
class ChatState {
|
|
final List<ChatMessage> messages;
|
|
final bool isLoading;
|
|
|
|
ChatState({required this.messages, this.isLoading = false});
|
|
|
|
ChatState copyWith({List<ChatMessage>? messages, bool? isLoading}) {
|
|
return ChatState(
|
|
messages: messages ?? this.messages,
|
|
isLoading: isLoading ?? this.isLoading,
|
|
);
|
|
}
|
|
}
|
|
|
|
class AiChatNotifier extends Notifier<ChatState> {
|
|
@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<void> 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 misión es educar a los ciudadanos sobre cómo separar la basura en 4 categorías: '
|
|
'Orgánicos (verde), Reciclables (azul), Sanitarios (naranja) y Especiales (morado). '
|
|
'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, ChatState>(
|
|
AiChatNotifier.new,
|
|
);
|