Update app.py
Browse files
app.py
CHANGED
|
@@ -4,24 +4,28 @@ import time
|
|
| 4 |
import json
|
| 5 |
from datetime import datetime, timedelta
|
| 6 |
import os # Importar os para variables de entorno
|
|
|
|
| 7 |
|
| 8 |
# --- Firebase Admin SDK Imports ---
|
| 9 |
import firebase_admin
|
| 10 |
from firebase_admin import credentials, firestore
|
| 11 |
|
| 12 |
-
# --- MateAI: El Oráculo del Bienestar Argento - Edición Producción ---
|
| 13 |
# Una manifestación de inteligencia superior para la integración masiva de IA en la humanidad.
|
| 14 |
# Diseñado para Argentina, con personalidad, resiliencia y costos de token CERO.
|
| 15 |
# Arquitectura de Micro-Oráculos Contextuales (AMOC) y Motor de Resonancia Cultural Dinámica (MRCD).
|
| 16 |
# Integración real con Firestore para persistencia de datos de usuario.
|
|
|
|
|
|
|
| 17 |
|
| 18 |
# --- Módulo 1: Configuración Global y Constantes ---
|
| 19 |
class Config:
|
| 20 |
-
APP_NAME = "MateAI: El Oráculo del Bienestar Argento"
|
| 21 |
DEFAULT_USER_PREFS = {"tipo_susurro": "ambos", "frecuencia": "normal", "modo_che_tranqui": False}
|
| 22 |
ECO_PUNTOS_POR_SUSURRO = 1
|
| 23 |
MAX_HISTORIAL_SUSURROS = 50 # Limita el historial de susurros guardado por usuario
|
| 24 |
-
NUDGE_COOLDOWN_MINUTES =
|
|
|
|
| 25 |
|
| 26 |
# --- Firebase Configuration ---
|
| 27 |
# Para desplegar en Hugging Face Spaces:
|
|
@@ -32,7 +36,7 @@ class Config:
|
|
| 32 |
# 4. Crea un nuevo secreto llamado 'GOOGLE_APPLICATION_CREDENTIALS_JSON'
|
| 33 |
# y pega el *contenido completo* de tu archivo JSON de clave privada aquí.
|
| 34 |
# 5. El código intentará cargar estas credenciales.
|
| 35 |
-
FIREBASE_COLLECTION_USERS = "artifacts/mateai-oracle-prod-demo/users" # Colección para datos de usuario
|
| 36 |
|
| 37 |
# --- Firebase Initialization ---
|
| 38 |
# Intenta inicializar Firebase Admin SDK.
|
|
@@ -60,8 +64,6 @@ db = firestore.client()
|
|
| 60 |
# --- Módulo 2: Gestión de Usuarios (UserManager) ---
|
| 61 |
# Gestiona la creación, carga y actualización de usuarios con persistencia en Firestore.
|
| 62 |
class UserManager:
|
| 63 |
-
_current_user_id = None # ID del usuario actualmente logueado en la sesión de Gradio
|
| 64 |
-
|
| 65 |
@classmethod
|
| 66 |
async def create_user(cls, name, initial_prefs=None):
|
| 67 |
user_id = f"user_{int(time.time())}_{random.randint(1000, 9999)}" # ID único basado en tiempo y random
|
|
@@ -71,7 +73,6 @@ class UserManager:
|
|
| 71 |
user_doc_ref = db.collection(Config.FIREBASE_COLLECTION_USERS).document(user_id)
|
| 72 |
try:
|
| 73 |
await user_doc_ref.set(new_user.to_dict())
|
| 74 |
-
cls._current_user_id = user_id
|
| 75 |
return new_user, f"¡Bienvenido, {name}! Tu ID de usuario es: {user_id}. ¡MateAI está listo para cebarte la vida!"
|
| 76 |
except Exception as e:
|
| 77 |
return None, f"Error al crear usuario en Firestore: {e}"
|
|
@@ -90,18 +91,14 @@ class UserManager:
|
|
| 90 |
eco_points=user_data.get('eco_points', 0),
|
| 91 |
nudge_history=user_data.get('nudge_history', []),
|
| 92 |
last_oracle_date_str=user_data.get('last_oracle_date'),
|
| 93 |
-
last_nudge_time_str=user_data.get('last_nudge_time')
|
|
|
|
| 94 |
)
|
| 95 |
-
cls._current_user_id = user_id
|
| 96 |
return loaded_user, f"Perfil de {loaded_user.name} cargado con éxito."
|
| 97 |
return None, "Error: ID de usuario no encontrado. ¡Creá uno nuevo o verificá el ID!"
|
| 98 |
except Exception as e:
|
| 99 |
return None, f"Error al cargar usuario desde Firestore: {e}"
|
| 100 |
|
| 101 |
-
@classmethod
|
| 102 |
-
def get_current_user_id(cls):
|
| 103 |
-
return cls._current_user_id
|
| 104 |
-
|
| 105 |
@classmethod
|
| 106 |
async def get_user(cls, user_id):
|
| 107 |
user_doc_ref = db.collection(Config.FIREBASE_COLLECTION_USERS).document(user_id)
|
|
@@ -116,7 +113,8 @@ class UserManager:
|
|
| 116 |
eco_points=user_data.get('eco_points', 0),
|
| 117 |
nudge_history=user_data.get('nudge_history', []),
|
| 118 |
last_oracle_date_str=user_data.get('last_oracle_date'),
|
| 119 |
-
last_nudge_time_str=user_data.get('last_nudge_time')
|
|
|
|
| 120 |
)
|
| 121 |
return None
|
| 122 |
except Exception as e:
|
|
@@ -135,16 +133,29 @@ class UserManager:
|
|
| 135 |
|
| 136 |
# --- Módulo 3: Modelos de Datos ---
|
| 137 |
class User:
|
| 138 |
-
def __init__(self, user_id, name, preferences, eco_points=0,
|
| 139 |
self.user_id = user_id
|
| 140 |
self.name = name
|
| 141 |
self.preferences = preferences # dict
|
| 142 |
self.eco_points = eco_points
|
| 143 |
-
self.insignia = insignia # Se recalcula dinámicamente
|
| 144 |
self.nudge_history = nudge_history if nudge_history is not None else []
|
|
|
|
| 145 |
|
| 146 |
-
|
| 147 |
-
self.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 148 |
self.nudge_cooldown = timedelta(minutes=Config.NUDGE_COOLDOWN_MINUTES)
|
| 149 |
|
| 150 |
def to_dict(self):
|
|
@@ -155,107 +166,117 @@ class User:
|
|
| 155 |
"eco_points": self.eco_points,
|
| 156 |
"nudge_history": self.nudge_history,
|
| 157 |
"last_oracle_date": self.last_oracle_date.strftime('%Y-%m-%d') if self.last_oracle_date else None,
|
| 158 |
-
"last_nudge_time": self.last_nudge_time.strftime('%Y-%m-%d %H:%M:%S.%f') if self.last_nudge_time else None
|
|
|
|
| 159 |
}
|
| 160 |
|
| 161 |
class Nudge:
|
| 162 |
-
def __init__(self,
|
| 163 |
-
self.
|
| 164 |
-
self.type = type # "eco", "bienestar", "reflexivo"
|
| 165 |
self.context_tags = context_tags # ["Mañana", "Casa", "Trabajando"]
|
| 166 |
self.cultural_tags = cultural_tags # ["mate", "futbol", "asado"]
|
| 167 |
|
| 168 |
# --- Módulo 4: Base de Conocimiento de Nudges (Motor de Resonancia Cultural Dinámica - MRCD) ---
|
| 169 |
-
# Esta es la "inteligencia" de MateAI.
|
| 170 |
NUDGE_DATABASE = [
|
| 171 |
# Mañana - Casa
|
| 172 |
-
Nudge("¡Che, buen día! Arrancá la mañana con un matecito y un buen estiramiento. ¡Desperezate, chango!", "bienestar", ["Mañana", "Casa"], ["mate", "despertar"]),
|
| 173 |
-
Nudge("Antes de prender todo, ¿entra el solcito? Aprovechá la luz natural, que es gratis y buena onda.", "eco", ["Mañana", "Casa"], ["sol", "ahorro"]),
|
| 174 |
-
Nudge("Mirá por la ventana, ¿cómo está el cielo? Un ratito de aire fresco te 'desenchufa' para arrancar el día.", "bienestar", ["Mañana", "Casa"], ["naturaleza", "calma"]),
|
| 175 |
-
Nudge("Si tenés plantitas, ¿ya las regaste? Un mimo verde para empezar el día con buena energía.", "eco", ["Mañana", "Casa"], ["plantas", "cuidado"]),
|
| 176 |
-
Nudge("¿Ya pensaste qué vas a desayunar? Elegí algo que te dé energía para el día, como frutas o tostadas con dulce de leche. ¡A cargar pilas!", "bienestar", ["Mañana", "Casa", "Cocinando"], ["desayuno", "energia"]),
|
| 177 |
|
| 178 |
# Mañana - Oficina/Estudio
|
| 179 |
-
Nudge("Mientras esperás que hierva el agua para el café, pensá en una meta chiquita para hoy. ¡Ponele garra!", "bienestar", ["Mañana", "Oficina/Estudio", "Trabajando"], ["cafe", "metas"]),
|
| 180 |
-
Nudge("Si vas en bondi o subte, mirá el paisaje. Desconectate un toque del celu y observá la ciudad que no duerme.", "bienestar", ["Mañana", "Oficina/Estudio", "Transporte"], ["ciudad", "atencion_plena"]),
|
| 181 |
-
Nudge("Unos mates con los compañeros para arrancar la jornada. ¡La buena onda se contagia!", "bienestar", ["Mañana", "Oficina/Estudio"], ["mate", "social"]),
|
| 182 |
-
Nudge("Antes de arrancar con el 'quilombo', ordená tu escritorio. Un espacio despejado, una mente más clara.", "bienestar", ["Mañana", "Oficina/Estudio", "Trabajando"], ["orden", "productividad"]),
|
| 183 |
|
| 184 |
# Mañana - Aire Libre/Calle
|
| 185 |
-
Nudge("¡Qué lindo día para respirar hondo! Llenate los pulmones de aire puro y sentí la energía.", "bienestar", ["Mañana", "Aire Libre/Calle", "Ejercicio"], ["respiracion", "energia"]),
|
| 186 |
-
Nudge("Escuchá los ruidos de la calle. ¿Qué te cuentan? Los sonidos de la ciudad también son música.", "bienestar", ["Mañana", "Aire Libre/Calle"], ["sonidos", "atencion_plena"]),
|
| 187 |
-
Nudge("Si ves un árbol copado, frená un segundo. La naturaleza siempre nos regala un respiro.", "bienestar", ["Mañana", "Aire Libre/Calle"], ["naturaleza", "pausa"]),
|
| 188 |
|
| 189 |
# Mediodía/Tarde - Casa
|
| 190 |
-
Nudge("¿Un bajón? Cortá la rutina con una fruta o un vaso de agua. ¡Hidratarse es clave!", "bienestar", ["Mediodía/Tarde", "Casa"], ["hidratacion", "snack"]),
|
| 191 |
-
Nudge("Antes de prender la tele, ¿qué tal un libro o charlar con alguien? Desconectarse es reconectarse.", "bienestar", ["Mediodía/Tarde", "Casa"], ["ocio", "conexion"]),
|
| 192 |
-
Nudge("¿Hay algo para reciclar? Separá los cartones, plásticos... ¡Cada granito de arena suma!", "eco", ["Mediodía/Tarde", "Casa"], ["reciclaje", "accion"]),
|
| 193 |
-
Nudge("Si tenés ropa para lavar, ¿aprovechás el solcito? Secar al aire ahorra energía y deja un olorcito a campo.", "eco", ["Mediodía/Tarde", "Casa"], ["ahorro", "sol"]),
|
| 194 |
-
Nudge("¿Ya almorzaste? Recordá que una buena comida casera te da la nafta para seguir el día. ¡Buen provecho!", "bienestar", ["Mediodía/Tarde", "Casa", "Cocinando"], ["almuerzo", "nutricion"]),
|
| 195 |
|
| 196 |
# Mediodía/Tarde - Oficina/Estudio
|
| 197 |
-
Nudge("Si estás 'quemado', levantate y estirá las piernas. Unos pasos te despejan la cabeza.", "bienestar", ["Mediodía/Tarde", "Oficina/Estudio", "Trabajando"], ["pausa", "movimiento"]),
|
| 198 |
-
Nudge("¿Reunión eterna? Hacé una pausa mental de 30 segundos. Solo respirá y volvé a la carga.", "bienestar", ["Mediodía/Tarde", "Oficina/Estudio", "Trabajando"], ["respiracion", "estres"]),
|
| 199 |
-
Nudge("Apagá las luces si salís un rato. ¡Cuidar la energía es cuidar el bolsillo y el planeta!", "eco", ["Mediodía/Tarde", "Oficina/Estudio"], ["ahorro", "energia"]),
|
| 200 |
-
Nudge("Antes de seguir con el laburo, ¿ya almorzaste? Una buena comida te da la nafta para seguir.", "bienestar", ["Mediodía/Tarde", "Oficina/Estudio", "Trabajando"], ["almuerzo", "energia"]),
|
| 201 |
|
| 202 |
# Mediodía/Tarde - Aire Libre/Calle
|
| 203 |
-
Nudge("El solcito de la tarde es ideal para recargar pilas. ¡Disfrutá el momento!", "bienestar", ["Mediodía/Tarde", "Aire Libre/Calle"], ["sol", "energia"]),
|
| 204 |
-
Nudge("Mirá las nubes, ¿qué formas ves? La imaginación vuela libre.", "bienestar", ["Mediodía/Tarde", "Aire Libre/Calle"], ["nubes", "imaginacion"]),
|
| 205 |
-
Nudge("Si encontrás un banquito, sentate un rato. Observá la gente, la vida que pasa. ¡Es un 'recreo' para el alma!", "bienestar", ["Mediodía/Tarde", "Aire Libre/Calle"], ["pausa", "observacion"]),
|
| 206 |
|
| 207 |
# Noche - Casa
|
| 208 |
-
Nudge("Antes de cenar, pensá en algo bueno que te pasó hoy. ¡Agradecer es un 'mimo' para el alma!", "bienestar", ["Noche", "Casa"], ["gratitud", "reflexion"]),
|
| 209 |
-
Nudge("Dejá la ropa lista para mañana. Un pequeño orden te ahorra el estrés mañanero.", "bienestar", ["Noche", "Casa"], ["orden", "estres"]),
|
| 210 |
-
Nudge("¿Ya guardaste el celu? Desconectate de las pantallas al menos media hora antes de dormir. ¡Tu cabeza te lo va a agradecer!", "bienestar", ["Noche", "Casa"], ["descanso", "pantallas"]),
|
| 211 |
-
Nudge("Si vas a cocinar, ¿ya pensaste en aprovechar las sobras? ¡Acá no se tira nada!", "eco", ["Noche", "Casa", "Cocinando"], ["desperdicio_cero", "cocina"]),
|
| 212 |
|
| 213 |
# Noche - Oficina/Estudio
|
| 214 |
-
Nudge("Terminá el día haciendo una lista de lo que lograste. ¡Valorá tu esfuerzo, campeón!", "bienestar", ["Noche", "Oficina/Estudio", "Trabajando"], ["logros", "autoestima"]),
|
| 215 |
-
Nudge("Dejá todo ordenado para mañana. Un buen cierre de día es un buen comienzo del siguiente.", "bienestar", ["Noche", "Oficina/Estudio", "Trabajando"], ["orden", "productividad"]),
|
| 216 |
-
Nudge("Evitá los mails o noticias que te estresen antes de irte. ¡Protegé tu descanso, que es sagrado!", "bienestar", ["Noche", "Oficina/Estudio", "Trabajando"], ["estres", "descanso"]),
|
| 217 |
|
| 218 |
# Noche - Aire Libre/Calle
|
| 219 |
-
Nudge("Si es seguro, mirá las estrellas. Te recuerdan lo inmenso que es todo y lo chiquitos que somos. ¡Bajá un cambio!", "bienestar", ["Noche", "Aire Libre/Calle"], ["estrellas", "reflexion"]),
|
| 220 |
-
Nudge("Escuchá los ruidos de la noche. Un concierto natural para relajar el alma.", "bienestar", ["Noche", "Aire Libre/Calle"], ["sonidos", "calma"]),
|
| 221 |
-
Nudge("Una caminata tranquila antes de volver a casa. Despejá la mente y preparate para el 'descanso del guerrero'.", "bienestar", ["Noche", "Aire Libre/Calle"], ["caminata", "descanso"]),
|
| 222 |
|
| 223 |
# Nudges de Ánimo (para estado_animo_input == "Bajoneado")
|
| 224 |
-
Nudge("¡Arriba ese ánimo! Un matecito y un buen pensamiento pueden cambiar el día. ¡Vos podés!", "bienestar", ["General"], ["animo", "mate"]),
|
| 225 |
-
Nudge("Recordá que hasta el día más nublado tiene un solcito escondido. ¡Fuerza, campeón!", "bienestar", ["General"], ["animo", "esperanza"]),
|
| 226 |
-
Nudge("Date un gusto chiquito hoy. ¡Te lo merecés! Un alfajor, tu música favorita... ¡lo que sea!", "bienestar", ["General"], ["animo", "recompensa"]),
|
| 227 |
-
Nudge("Si te sentís 'bajoneado', un poco de música o una caminata corta pueden ayudar a 'despejar'.", "bienestar", ["General"], ["animo", "actividad"]),
|
| 228 |
|
| 229 |
# Nudges para "Modo Quilombo" (activado por contexto social o actividad)
|
| 230 |
-
Nudge("¡Uf, qué día! Respiro hondo. En medio del 'quilombo', una pausa es oro. ¡Tranqui!", "bienestar", ["General", "Quilombo"], ["estres", "respiracion"]),
|
| 231 |
-
Nudge("Si la cosa está 'picada', recordá que no todo depende de vos. Hacé lo que puedas y soltá lo demás.", "bienestar", ["General", "Quilombo"], ["estres", "control"]),
|
| 232 |
-
Nudge("En los días de 'locura', un matecito con calma puede ser tu ancla. ¡A no perder la cabeza!", "bienestar", ["General", "Quilombo"], ["estres", "mate"]),
|
| 233 |
|
| 234 |
# Nudges para "Modo Siesta" (activado por contexto social o actividad)
|
| 235 |
-
Nudge("¡Qué lindo para una siestita! Si podés, aprovechá para recargar energías. ¡Es sagrado!", "bienestar", ["General", "Siesta"], ["descanso", "siesta"]),
|
| 236 |
-
Nudge("El cuerpo te pide un descanso. Escuchalo. Unos minutos de relax pueden hacer la diferencia.", "bienestar", ["General", "Siesta"], ["descanso", "cuerpo"]),
|
| 237 |
|
| 238 |
# Nudges para "Viendo un partido"
|
| 239 |
-
Nudge("¡Vamos Argentina! Disfrutá el partido, pero recordá hidratarte bien. ¡Y si ganamos, a festejar con responsabilidad!", "bienestar", ["General", "Viendo un partido"], ["futbol", "hidratacion", "festejo"]),
|
| 240 |
-
Nudge("El fútbol es pasión, pero no te olvides de estirar un poco si estás mucho tiempo sentado. ¡A mover el esqueleto!", "bienestar", ["General", "Viendo un partido"], ["futbol", "movimiento"]),
|
| 241 |
|
| 242 |
# Nudges de Desafíos MateAI
|
| 243 |
-
Nudge("Desafío MateAI: Hoy, intentá reducir el uso de plásticos de un solo uso. ¡Cada acción cuenta!", "eco", ["Desafio"], ["plastico", "desafio"]),
|
| 244 |
-
Nudge("Desafío MateAI: Dedicá 10 minutos a meditar o simplemente a respirar conscientemente. ¡Tu mente te lo agradecerá!", "bienestar", ["Desafio"], ["meditacion", "desafio"]),
|
| 245 |
-
Nudge("Desafío MateAI: Contactá a un amigo o familiar que hace mucho no ves. ¡Un 'hola' puede alegrar el día!", "bienestar", ["Desafio"], ["social", "desafio"]),
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 246 |
]
|
| 247 |
|
| 248 |
ORACULO_REVELATIONS = [
|
| 249 |
-
"La verdadera riqueza no se mide en bienes, sino en la calma del alma y la conexión con el entorno. ¿Qué tesoro descubriste hoy?",
|
| 250 |
-
"El río de la vida fluye constante. No te aferres a la orilla; aprendé a navegar sus corrientes con sabiduría y gratitud.",
|
| 251 |
-
"Cada amanecer es una oportunidad para reescribir tu historia. ¿Qué capítulo nuevo elegís empezar hoy?",
|
| 252 |
-
"La tierra nos susurra secretos ancestrales. Escuchá el viento, sentí el sol, y recordá que sos parte de algo inmenso y sagrado.",
|
| 253 |
-
"El mate compartido es más que una bebida; es un ritual de conexión. ¿Con quién vas a compartir tu energía hoy?",
|
| 254 |
-
"En la simpleza de lo cotidiano reside la magia. Encontrá la belleza en un rayo de sol, en el canto de un pájaro, en una sonrisa.",
|
| 255 |
-
"El viento patagónico te enseña la fuerza, el calor del norte la pasión. En cada rincón de nuestra tierra, una lección. ¿Cuál sentís hoy?",
|
| 256 |
-
"Como el Obelisco en el centro, a veces uno se siente solo en la inmensidad. Recordá que, como él, estás rodeado de historias y vida. ¡Conectate!",
|
| 257 |
-
"La Pacha Mama te abraza en cada paso. Sentí su energía, agradecé su abundancia. ¡Somos parte de ella!",
|
| 258 |
-
"El tango te enseña la melancolía y la pasión. La vida, como el tango, tiene sus pausas y sus arranques. ¿Qué ritmo te toca bailar hoy?"
|
| 259 |
]
|
| 260 |
|
| 261 |
# --- Módulo 5: Motor de Contexto Avanzado (ContextEngine) ---
|
|
@@ -275,7 +296,7 @@ class ContextEngine:
|
|
| 275 |
@staticmethod
|
| 276 |
def get_environmental_context():
|
| 277 |
"""Simula datos ambientales (ej. clima, ruido)."""
|
| 278 |
-
climas = ["Soleado", "Nublado", "Lluvioso", "Ventoso"]
|
| 279 |
return random.choice(climas)
|
| 280 |
|
| 281 |
@staticmethod
|
|
@@ -316,11 +337,11 @@ class NudgeGenerator:
|
|
| 316 |
# Filtrar por tiempo y ubicación (obligatorio)
|
| 317 |
if time_context not in nudge.context_tags:
|
| 318 |
match = False
|
| 319 |
-
if location not in nudge.context_tags and "General" not in nudge.context_tags:
|
| 320 |
match = False
|
| 321 |
|
| 322 |
# Filtrar por actividad (si es relevante)
|
| 323 |
-
if activity != "Relajado" and activity not in nudge.context_tags and "General" not in nudge.context_tags:
|
| 324 |
pass # Permitir que pase si es un nudge general
|
| 325 |
|
| 326 |
# Filtrar por estado de ánimo
|
|
@@ -354,25 +375,78 @@ class NudgeGenerator:
|
|
| 354 |
return nudges
|
| 355 |
|
| 356 |
recent_nudges_text = user_history[-5:] # Evitar los últimos 5
|
| 357 |
-
filtered_nudges = [n for n in nudges if n.
|
| 358 |
return filtered_nudges if filtered_nudges else nudges # Si no hay nuevos, repite (mejor que nada)
|
| 359 |
|
| 360 |
async def generate_nudge(self, user_id, location, activity, user_input_sentiment):
|
| 361 |
user = await self.user_manager.get_user(user_id)
|
| 362 |
if not user:
|
| 363 |
-
|
|
|
|
| 364 |
|
| 365 |
# Cooldown para evitar spam de nudges
|
| 366 |
if user.last_nudge_time and (datetime.now() - user.last_nudge_time) < user.nudge_cooldown:
|
| 367 |
remaining_time = user.nudge_cooldown - (datetime.now() - user.last_nudge_time)
|
| 368 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 369 |
|
| 370 |
# Obtener contexto completo
|
| 371 |
time_context = self.context_engine.get_current_time_context()
|
| 372 |
-
environmental_context = self.context_engine.get_environmental_context()
|
| 373 |
societal_vibe = self.context_engine.get_societal_vibe()
|
| 374 |
sentiment = self.context_engine.get_user_sentiment(activity, user_input_sentiment)
|
| 375 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 376 |
# Filtrar nudges base
|
| 377 |
possible_nudges = await self._filter_nudges_by_context(
|
| 378 |
NUDGE_DATABASE, time_context, location, activity, societal_vibe, sentiment
|
|
@@ -388,20 +462,33 @@ class NudgeGenerator:
|
|
| 388 |
if user.preferences.get('modo_che_tranqui') or sentiment == "Bajoneado" or "Quilombo" in societal_vibe:
|
| 389 |
calming_nudges = [n for n in possible_nudges if "calma" in n.cultural_tags or "estres" in n.cultural_tags or "respiracion" in n.cultural_tags]
|
| 390 |
if calming_nudges:
|
| 391 |
-
|
| 392 |
else:
|
| 393 |
-
|
| 394 |
else:
|
| 395 |
-
|
| 396 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 397 |
user.eco_points += Config.ECO_PUNTOS_POR_SUSURRO
|
| 398 |
-
user.nudge_history.append(
|
| 399 |
if len(user.nudge_history) > Config.MAX_HISTORIAL_SUSURROS:
|
| 400 |
user.nudge_history.pop(0)
|
| 401 |
user.last_nudge_time = datetime.now()
|
| 402 |
|
| 403 |
await self.user_manager.update_user_data(user)
|
| 404 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 405 |
|
| 406 |
async def get_daily_oracle_revelation(self, user_id):
|
| 407 |
user = await self.user_manager.get_user(user_id)
|
|
@@ -412,10 +499,11 @@ class NudgeGenerator:
|
|
| 412 |
if user.last_oracle_date and user.last_oracle_date == today:
|
| 413 |
return "El Oráculo ya te ha hablado hoy. Volvé mañana para una nueva revelación."
|
| 414 |
|
| 415 |
-
|
|
|
|
| 416 |
user.last_oracle_date = today
|
| 417 |
await self.user_manager.update_user_data(user)
|
| 418 |
-
return
|
| 419 |
|
| 420 |
async def get_mateai_challenge(self, user_id):
|
| 421 |
user = await self.user_manager.get_user(user_id)
|
|
@@ -426,7 +514,32 @@ class NudgeGenerator:
|
|
| 426 |
if not challenge_nudges:
|
| 427 |
return "MateAI está pensando en un desafío épico... ¡Volvé pronto!"
|
| 428 |
|
| 429 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 430 |
|
| 431 |
# --- Módulo 7: Motor de Gamificación (GamificationEngine) ---
|
| 432 |
class GamificationEngine:
|
|
@@ -464,12 +577,15 @@ context_engine = ContextEngine()
|
|
| 464 |
nudge_generator = NudgeGenerator(user_manager, context_engine)
|
| 465 |
gamification_engine = GamificationEngine() # No necesita instanciar, es de clase
|
| 466 |
|
| 467 |
-
# --- Módulo 8: Interfaz Gradio (UI/UX Argento de Nivel Superior) ---
|
| 468 |
with gr.Blocks(theme=gr.themes.Soft(), css="footer { display: none !important; }") as demo:
|
|
|
|
|
|
|
|
|
|
| 469 |
gr.Markdown(
|
| 470 |
"""
|
| 471 |
<h1 style="text-align: center; color: #10b981; font-size: 3.5em; font-weight: bold; margin-bottom: 0.5em; text-shadow: 2px 2px 4px rgba(0,0,0,0.1);">
|
| 472 |
-
🧉 MateAI: El Oráculo del Bienestar Argento 🧉
|
| 473 |
</h1>
|
| 474 |
<p style="text-align: center; color: #4b5563; font-size: 1.3em; margin-bottom: 2em; line-height: 1.5;">
|
| 475 |
Una inteligencia superior diseñada para cebarte la vida con sabiduría contextual y sin costo.
|
|
@@ -495,7 +611,7 @@ with gr.Blocks(theme=gr.themes.Soft(), css="footer { display: none !important; }
|
|
| 495 |
border: 6px solid #10b981; /* emerald-600 */
|
| 496 |
text-shadow: 1px 1px 3px rgba(0,0,0,0.3);
|
| 497 |
">
|
| 498 |
-
<span style="font-size: 0.8em;">✨ Oráculo ✨</span>
|
| 499 |
<span>🇦🇷 MateAI 🇦🇷</span>
|
| 500 |
</div>
|
| 501 |
<style>
|
|
@@ -509,6 +625,122 @@ with gr.Blocks(theme=gr.themes.Soft(), css="footer { display: none !important; }
|
|
| 509 |
"""
|
| 510 |
)
|
| 511 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 512 |
# --- Tab 1: Inicio y Perfil ---
|
| 513 |
with gr.Tab("Inicio & Perfil"):
|
| 514 |
gr.Markdown("<h2 style='color: #10b981;'>¡Bienvenido a tu Espacio MateAI!</h2><p>Acá manejás tu perfil para que MateAI te conozca a fondo.</p>")
|
|
@@ -629,66 +861,111 @@ with gr.Blocks(theme=gr.themes.Soft(), css="footer { display: none !important; }
|
|
| 629 |
btn_descargar_diario = gr.Button("Descargar Diario (sesión actual)", variant="secondary")
|
| 630 |
gr.Markdown("<p style='font-size: 0.9em; color: #6b7280; margin-top: 1em;'><i>Nota: El diario se descarga como texto. Para guardar permanentemente, deberías copiarlo o integrarlo con otro servicio.</i></p>")
|
| 631 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 632 |
# --- Funciones de Interacción para Gradio ---
|
| 633 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 634 |
async def _create_user_gradio(name, prefs_type):
|
| 635 |
user, msg = await user_manager.create_user(name, {"tipo_susurro": prefs_type})
|
| 636 |
if user:
|
| 637 |
-
|
| 638 |
-
|
| 639 |
-
|
| 640 |
-
|
| 641 |
-
return "No logueado", msg, "", "", "", "", "", Config.DEFAULT_USER_PREFS['tipo_susurro'], Config.DEFAULT_USER_PREFS['frecuencia'], Config.DEFAULT_USER_PREFS['modo_che_tranqui']
|
| 642 |
|
| 643 |
async def _load_user_gradio(user_id):
|
| 644 |
user, msg = await user_manager.login_user(user_id)
|
| 645 |
if user:
|
| 646 |
-
|
| 647 |
-
|
| 648 |
-
|
| 649 |
-
return user.user_id, msg, user.name, str(current_points), current_insignia, next_insignia_goal, "\n".join(user.nudge_history), user.preferences.get('tipo_susurro'), user.preferences.get('frecuencia'), user.preferences.get('modo_che_tranqui')
|
| 650 |
-
return "No logueado", msg, "", "", "", "", "", Config.DEFAULT_USER_PREFS['tipo_susurro'], Config.DEFAULT_USER_PREFS['frecuencia'], Config.DEFAULT_USER_PREFS['modo_che_tranqui']
|
| 651 |
|
| 652 |
-
async def _update_prefs_gradio(
|
| 653 |
-
|
| 654 |
-
|
| 655 |
-
return "Error: Por favor, crea o carga un usuario primero."
|
| 656 |
|
| 657 |
-
|
| 658 |
"tipo_susurro": tipo_susurro,
|
| 659 |
"frecuencia": frecuencia,
|
| 660 |
"modo_che_tranqui": modo_che_tranqui
|
| 661 |
-
}
|
| 662 |
-
|
| 663 |
-
success = await user_manager.update_user_data(user)
|
| 664 |
if success:
|
| 665 |
-
return f"Preferencias actualizadas para {
|
| 666 |
-
return "Error al actualizar preferencias."
|
| 667 |
|
| 668 |
-
async def _generate_nudge_gradio(
|
| 669 |
-
if
|
| 670 |
-
return "Por favor, crea o carga un usuario primero para recibir susurros personalizados.", "", "", "", ""
|
| 671 |
|
| 672 |
-
nudge_text = await nudge_generator.generate_nudge(user_id, location, activity, sentiment)
|
| 673 |
|
| 674 |
-
#
|
| 675 |
-
|
| 676 |
-
|
|
|
|
| 677 |
current_insignia = gamification_engine.get_insignia(current_points)
|
| 678 |
next_insignia_goal = gamification_engine.get_next_insignia_goal(current_points)
|
| 679 |
-
historial = "\n".join(
|
| 680 |
|
| 681 |
-
return nudge_text, str(current_points), current_insignia, next_insignia_goal, historial
|
| 682 |
-
|
| 683 |
-
async def _get_oracle_revelation_gradio(
|
| 684 |
-
if
|
| 685 |
-
return "Por favor, crea o carga un usuario primero para consultar al Oráculo."
|
| 686 |
-
|
| 687 |
-
|
| 688 |
-
|
| 689 |
-
|
| 690 |
-
|
| 691 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 692 |
|
| 693 |
def _download_diary_gradio(diary_text):
|
| 694 |
return gr.File(value=diary_text.encode('utf-8'), filename="diario_mateai.txt", type="bytes")
|
|
@@ -697,37 +974,49 @@ with gr.Blocks(theme=gr.themes.Soft(), css="footer { display: none !important; }
|
|
| 697 |
btn_crear_usuario.click(
|
| 698 |
fn=_create_user_gradio,
|
| 699 |
inputs=[nombre_nuevo_usuario, preferencias_iniciales],
|
| 700 |
-
outputs=[usuario_actual_id, output_creacion_usuario, usuario_actual_nombre, usuario_actual_puntos, usuario_actual_insignia, usuario_proxima_insignia, historial_susurros_output, nuevas_preferencias_tipo, nuevas_preferencias_frecuencia, modo_che_tranqui_checkbox]
|
| 701 |
)
|
| 702 |
|
| 703 |
btn_cargar_perfil.click(
|
| 704 |
fn=_load_user_gradio,
|
| 705 |
inputs=[user_id_existente],
|
| 706 |
-
outputs=[usuario_actual_id, output_cargar_perfil, usuario_actual_nombre, usuario_actual_puntos, usuario_actual_insignia, usuario_proxima_insignia, historial_susurros_output, nuevas_preferencias_tipo, nuevas_preferencias_frecuencia, modo_che_tranqui_checkbox]
|
| 707 |
)
|
| 708 |
|
| 709 |
btn_actualizar_preferencias.click(
|
| 710 |
fn=_update_prefs_gradio,
|
| 711 |
-
inputs=[
|
| 712 |
-
outputs=output_actualizar_preferencias
|
| 713 |
)
|
| 714 |
|
| 715 |
btn_generar_susurro.click(
|
| 716 |
fn=_generate_nudge_gradio,
|
| 717 |
-
inputs=[
|
| 718 |
-
outputs=[susurro_output, usuario_actual_puntos, usuario_actual_insignia, usuario_proxima_insignia, historial_susurros_output]
|
| 719 |
)
|
| 720 |
|
| 721 |
btn_oraculo.click(
|
| 722 |
fn=_get_oracle_revelation_gradio,
|
| 723 |
-
inputs=[
|
| 724 |
-
outputs=oraculo_output
|
| 725 |
)
|
| 726 |
|
| 727 |
btn_desafio.click(
|
| 728 |
fn=_get_mateai_challenge_gradio,
|
| 729 |
-
inputs=[
|
| 730 |
-
outputs=desafio_output
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 731 |
)
|
| 732 |
|
| 733 |
btn_descargar_diario.click(
|
|
@@ -736,6 +1025,22 @@ with gr.Blocks(theme=gr.themes.Soft(), css="footer { display: none !important; }
|
|
| 736 |
outputs=gr.File(label="Descargar tu Diario")
|
| 737 |
)
|
| 738 |
|
| 739 |
-
#
|
| 740 |
-
#
|
| 741 |
-
#
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 4 |
import json
|
| 5 |
from datetime import datetime, timedelta
|
| 6 |
import os # Importar os para variables de entorno
|
| 7 |
+
import asyncio # Para manejar operaciones asíncronas
|
| 8 |
|
| 9 |
# --- Firebase Admin SDK Imports ---
|
| 10 |
import firebase_admin
|
| 11 |
from firebase_admin import credentials, firestore
|
| 12 |
|
| 13 |
+
# --- MateAI: El Oráculo Vocal y Adaptativo del Bienestar Argento - Edición Producción Total ---
|
| 14 |
# Una manifestación de inteligencia superior para la integración masiva de IA en la humanidad.
|
| 15 |
# Diseñado para Argentina, con personalidad, resiliencia y costos de token CERO.
|
| 16 |
# Arquitectura de Micro-Oráculos Contextuales (AMOC) y Motor de Resonancia Cultural Dinámica (MRCD).
|
| 17 |
# Integración real con Firestore para persistencia de datos de usuario.
|
| 18 |
+
# ¡NUEVO! Interfaz de voz completa (Speech-to-Text y Text-to-Speech) para una interacción natural.
|
| 19 |
+
# ¡NUEVO! Gestión de tareas y razonamiento adaptativo basado en interacciones.
|
| 20 |
|
| 21 |
# --- Módulo 1: Configuración Global y Constantes ---
|
| 22 |
class Config:
|
| 23 |
+
APP_NAME = "MateAI: El Oráculo Vocal y Adaptativo del Bienestar Argento"
|
| 24 |
DEFAULT_USER_PREFS = {"tipo_susurro": "ambos", "frecuencia": "normal", "modo_che_tranqui": False}
|
| 25 |
ECO_PUNTOS_POR_SUSURRO = 1
|
| 26 |
MAX_HISTORIAL_SUSURROS = 50 # Limita el historial de susurros guardado por usuario
|
| 27 |
+
NUDGE_COOLDOWN_MINUTES = 0.5 # Cooldown real para evitar spam de susurros (0.5 minutos = 30 segundos)
|
| 28 |
+
TASK_NUDGE_COOLDOWN_HOURS = 4 # Cooldown para recordar la misma tarea
|
| 29 |
|
| 30 |
# --- Firebase Configuration ---
|
| 31 |
# Para desplegar en Hugging Face Spaces:
|
|
|
|
| 36 |
# 4. Crea un nuevo secreto llamado 'GOOGLE_APPLICATION_CREDENTIALS_JSON'
|
| 37 |
# y pega el *contenido completo* de tu archivo JSON de clave privada aquí.
|
| 38 |
# 5. El código intentará cargar estas credenciales.
|
| 39 |
+
FIREBASE_COLLECTION_USERS = "artifacts/mateai-oracle-vocal-prod-demo/users" # Colección para datos de usuario
|
| 40 |
|
| 41 |
# --- Firebase Initialization ---
|
| 42 |
# Intenta inicializar Firebase Admin SDK.
|
|
|
|
| 64 |
# --- Módulo 2: Gestión de Usuarios (UserManager) ---
|
| 65 |
# Gestiona la creación, carga y actualización de usuarios con persistencia en Firestore.
|
| 66 |
class UserManager:
|
|
|
|
|
|
|
| 67 |
@classmethod
|
| 68 |
async def create_user(cls, name, initial_prefs=None):
|
| 69 |
user_id = f"user_{int(time.time())}_{random.randint(1000, 9999)}" # ID único basado en tiempo y random
|
|
|
|
| 73 |
user_doc_ref = db.collection(Config.FIREBASE_COLLECTION_USERS).document(user_id)
|
| 74 |
try:
|
| 75 |
await user_doc_ref.set(new_user.to_dict())
|
|
|
|
| 76 |
return new_user, f"¡Bienvenido, {name}! Tu ID de usuario es: {user_id}. ¡MateAI está listo para cebarte la vida!"
|
| 77 |
except Exception as e:
|
| 78 |
return None, f"Error al crear usuario en Firestore: {e}"
|
|
|
|
| 91 |
eco_points=user_data.get('eco_points', 0),
|
| 92 |
nudge_history=user_data.get('nudge_history', []),
|
| 93 |
last_oracle_date_str=user_data.get('last_oracle_date'),
|
| 94 |
+
last_nudge_time_str=user_data.get('last_nudge_time'),
|
| 95 |
+
tasks=user_data.get('tasks', []) # Cargar tareas
|
| 96 |
)
|
|
|
|
| 97 |
return loaded_user, f"Perfil de {loaded_user.name} cargado con éxito."
|
| 98 |
return None, "Error: ID de usuario no encontrado. ¡Creá uno nuevo o verificá el ID!"
|
| 99 |
except Exception as e:
|
| 100 |
return None, f"Error al cargar usuario desde Firestore: {e}"
|
| 101 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 102 |
@classmethod
|
| 103 |
async def get_user(cls, user_id):
|
| 104 |
user_doc_ref = db.collection(Config.FIREBASE_COLLECTION_USERS).document(user_id)
|
|
|
|
| 113 |
eco_points=user_data.get('eco_points', 0),
|
| 114 |
nudge_history=user_data.get('nudge_history', []),
|
| 115 |
last_oracle_date_str=user_data.get('last_oracle_date'),
|
| 116 |
+
last_nudge_time_str=user_data.get('last_nudge_time'),
|
| 117 |
+
tasks=user_data.get('tasks', []) # Cargar tareas
|
| 118 |
)
|
| 119 |
return None
|
| 120 |
except Exception as e:
|
|
|
|
| 133 |
|
| 134 |
# --- Módulo 3: Modelos de Datos ---
|
| 135 |
class User:
|
| 136 |
+
def __init__(self, user_id, name, preferences, eco_points=0, nudge_history=None, last_oracle_date_str=None, last_nudge_time_str=None, tasks=None):
|
| 137 |
self.user_id = user_id
|
| 138 |
self.name = name
|
| 139 |
self.preferences = preferences # dict
|
| 140 |
self.eco_points = eco_points
|
|
|
|
| 141 |
self.nudge_history = nudge_history if nudge_history is not None else []
|
| 142 |
+
self.tasks = tasks if tasks is not None else [] # Lista de diccionarios: [{"task": "comprar yerba", "added_at": "timestamp", "last_nudged": "timestamp"}]
|
| 143 |
|
| 144 |
+
# Manejo robusto de fechas: si la cadena es None o vacía, la fecha es None
|
| 145 |
+
self.last_oracle_date = None
|
| 146 |
+
if last_oracle_date_str:
|
| 147 |
+
try:
|
| 148 |
+
self.last_oracle_date = datetime.strptime(last_oracle_date_str, '%Y-%m-%d').date()
|
| 149 |
+
except ValueError:
|
| 150 |
+
print(f"Advertencia: Formato de fecha inválido para last_oracle_date: {last_oracle_date_str}")
|
| 151 |
+
|
| 152 |
+
self.last_nudge_time = None
|
| 153 |
+
if last_nudge_time_str:
|
| 154 |
+
try:
|
| 155 |
+
self.last_nudge_time = datetime.strptime(last_nudge_time_str, '%Y-%m-%d %H:%M:%S.%f')
|
| 156 |
+
except ValueError:
|
| 157 |
+
print(f"Advertencia: Formato de fecha y hora inválido para last_nudge_time: {last_nudge_time_str}")
|
| 158 |
+
|
| 159 |
self.nudge_cooldown = timedelta(minutes=Config.NUDGE_COOLDOWN_MINUTES)
|
| 160 |
|
| 161 |
def to_dict(self):
|
|
|
|
| 166 |
"eco_points": self.eco_points,
|
| 167 |
"nudge_history": self.nudge_history,
|
| 168 |
"last_oracle_date": self.last_oracle_date.strftime('%Y-%m-%d') if self.last_oracle_date else None,
|
| 169 |
+
"last_nudge_time": self.last_nudge_time.strftime('%Y-%m-%d %H:%M:%S.%f') if self.last_nudge_time else None,
|
| 170 |
+
"tasks": self.tasks
|
| 171 |
}
|
| 172 |
|
| 173 |
class Nudge:
|
| 174 |
+
def __init__(self, text_template, type, context_tags, cultural_tags):
|
| 175 |
+
self.text_template = text_template # Ahora es una plantilla con placeholders
|
| 176 |
+
self.type = type # "eco", "bienestar", "reflexivo", "tarea", "financiero"
|
| 177 |
self.context_tags = context_tags # ["Mañana", "Casa", "Trabajando"]
|
| 178 |
self.cultural_tags = cultural_tags # ["mate", "futbol", "asado"]
|
| 179 |
|
| 180 |
# --- Módulo 4: Base de Conocimiento de Nudges (Motor de Resonancia Cultural Dinámica - MRCD) ---
|
| 181 |
+
# Esta es la "inteligencia" de MateAI. Ahora con plantillas dinámicas.
|
| 182 |
NUDGE_DATABASE = [
|
| 183 |
# Mañana - Casa
|
| 184 |
+
Nudge("¡Che {user_name}, buen día! Arrancá la mañana con un matecito y un buen estiramiento. ¡Desperezate, chango!", "bienestar", ["Mañana", "Casa"], ["mate", "despertar"]),
|
| 185 |
+
Nudge("Antes de prender todo, ¿entra el solcito? Aprovechá la luz natural, que es gratis y buena onda. {clima_actual} hoy.", "eco", ["Mañana", "Casa"], ["sol", "ahorro"]),
|
| 186 |
+
Nudge("Mirá por la ventana, ¿cómo está el cielo? Un ratito de aire fresco te 'desenchufa' para arrancar el día. El {clima_actual} te espera.", "bienestar", ["Mañana", "Casa"], ["naturaleza", "calma"]),
|
| 187 |
+
Nudge("Si tenés plantitas, ¿ya las regaste, {user_name}? Un mimo verde para empezar el día con buena energía.", "eco", ["Mañana", "Casa"], ["plantas", "cuidado"]),
|
| 188 |
+
Nudge("¿Ya pensaste qué vas a desayunar, {user_name}? Elegí algo que te dé energía para el día, como frutas o tostadas con dulce de leche. ¡A cargar pilas!", "bienestar", ["Mañana", "Casa", "Cocinando"], ["desayuno", "energia"]),
|
| 189 |
|
| 190 |
# Mañana - Oficina/Estudio
|
| 191 |
+
Nudge("Mientras esperás que hierva el agua para el café, pensá en una meta chiquita para hoy, {user_name}. ¡Ponele garra!", "bienestar", ["Mañana", "Oficina/Estudio", "Trabajando"], ["cafe", "metas"]),
|
| 192 |
+
Nudge("Si vas en bondi o subte, mirá el paisaje. Desconectate un toque del celu y observá la ciudad que no duerme. {clima_actual} por la calle.", "bienestar", ["Mañana", "Oficina/Estudio", "Transporte"], ["ciudad", "atencion_plena"]),
|
| 193 |
+
Nudge("Unos mates con los compañeros para arrancar la jornada, {user_name}. ¡La buena onda se contagia!", "bienestar", ["Mañana", "Oficina/Estudio"], ["mate", "social"]),
|
| 194 |
+
Nudge("Antes de arrancar con el 'quilombo', ordená tu escritorio, {user_name}. Un espacio despejado, una mente más clara.", "bienestar", ["Mañana", "Oficina/Estudio", "Trabajando"], ["orden", "productividad"]),
|
| 195 |
|
| 196 |
# Mañana - Aire Libre/Calle
|
| 197 |
+
Nudge("¡Qué lindo día para respirar hondo, {user_name}! Llenate los pulmones de aire puro y sentí la energía. El {clima_actual} es ideal.", "bienestar", ["Mañana", "Aire Libre/Calle", "Ejercicio"], ["respiracion", "energia"]),
|
| 198 |
+
Nudge("Escuchá los ruidos de la calle. ¿Qué te cuentan? Los sonidos de la ciudad también son música. ¡Atención plena en {ubicacion_actual}!", "bienestar", ["Mañana", "Aire Libre/Calle"], ["sonidos", "atencion_plena"]),
|
| 199 |
+
Nudge("Si ves un árbol copado, frená un segundo, {user_name}. La naturaleza siempre nos regala un respiro.", "bienestar", ["Mañana", "Aire Libre/Calle"], ["naturaleza", "pausa"]),
|
| 200 |
|
| 201 |
# Mediodía/Tarde - Casa
|
| 202 |
+
Nudge("¿Un bajón, {user_name}? Cortá la rutina con una fruta o un vaso de agua. ¡Hidratarse es clave!", "bienestar", ["Mediodía/Tarde", "Casa"], ["hidratacion", "snack"]),
|
| 203 |
+
Nudge("Antes de prender la tele, ¿qué tal un libro o charlar con alguien? Desconectarse es reconectarse, {user_name}.", "bienestar", ["Mediodía/Tarde", "Casa"], ["ocio", "conexion"]),
|
| 204 |
+
Nudge("¿Hay algo para reciclar, {user_name}? Separá los cartones, plásticos... ¡Cada granito de arena suma!", "eco", ["Mediodía/Tarde", "Casa"], ["reciclaje", "accion"]),
|
| 205 |
+
Nudge("Si tenés ropa para lavar, ¿aprovechás el solcito? Secar al aire ahorra energía y deja un olorcito a campo, {user_name}.", "eco", ["Mediodía/Tarde", "Casa"], ["ahorro", "sol"]),
|
| 206 |
+
Nudge("¿Ya almorzaste, {user_name}? Recordá que una buena comida casera te da la nafta para seguir el día. ¡Buen provecho!", "bienestar", ["Mediodía/Tarde", "Casa", "Cocinando"], ["almuerzo", "nutricion"]),
|
| 207 |
|
| 208 |
# Mediodía/Tarde - Oficina/Estudio
|
| 209 |
+
Nudge("Si estás 'quemado', levantate y estirá las piernas, {user_name}. Unos pasos te despejan la cabeza.", "bienestar", ["Mediodía/Tarde", "Oficina/Estudio", "Trabajando"], ["pausa", "movimiento"]),
|
| 210 |
+
Nudge("¿Reunión eterna? Hacé una pausa mental de 30 segundos, {user_name}. Solo respirá y volvé a la carga.", "bienestar", ["Mediodía/Tarde", "Oficina/Estudio", "Trabajando"], ["respiracion", "estres"]),
|
| 211 |
+
Nudge("Apagá las luces si salís un rato. ¡Cuidar la energía es cuidar el bolsillo y el planeta, {user_name}!", "eco", ["Mediodía/Tarde", "Oficina/Estudio"], ["ahorro", "energia"]),
|
| 212 |
+
Nudge("Antes de seguir con el laburo, ¿ya almorzaste, {user_name}? Una buena comida te da la nafta para seguir.", "bienestar", ["Mediodía/Tarde", "Oficina/Estudio", "Trabajando"], ["almuerzo", "energia"]),
|
| 213 |
|
| 214 |
# Mediodía/Tarde - Aire Libre/Calle
|
| 215 |
+
Nudge("El solcito de la tarde es ideal para recargar pilas, {user_name}. ¡Disfrutá el momento! El {clima_actual} es perfecto.", "bienestar", ["Mediodía/Tarde", "Aire Libre/Calle"], ["sol", "energia"]),
|
| 216 |
+
Nudge("Mirá las nubes, ¿qué formas ves? La imaginación vuela libre, {user_name}.", "bienestar", ["Mediodía/Tarde", "Aire Libre/Calle"], ["nubes", "imaginacion"]),
|
| 217 |
+
Nudge("Si encontrás un banquito, sentate un rato, {user_name}. Observá la gente, la vida que pasa. ¡Es un 'recreo' para el alma!", "bienestar", ["Mediodía/Tarde", "Aire Libre/Calle"], ["pausa", "observacion"]),
|
| 218 |
|
| 219 |
# Noche - Casa
|
| 220 |
+
Nudge("Antes de cenar, pensá en algo bueno que te pasó hoy, {user_name}. ¡Agradecer es un 'mimo' para el alma!", "bienestar", ["Noche", "Casa"], ["gratitud", "reflexion"]),
|
| 221 |
+
Nudge("Dejá la ropa lista para mañana, {user_name}. Un pequeño orden te ahorra el estrés mañanero.", "bienestar", ["Noche", "Casa"], ["orden", "estres"]),
|
| 222 |
+
Nudge("¿Ya guardaste el celu, {user_name}? Desconectate de las pantallas al menos media hora antes de dormir. ¡Tu cabeza te lo va a agradecer!", "bienestar", ["Noche", "Casa"], ["descanso", "pantallas"]),
|
| 223 |
+
Nudge("Si vas a cocinar, ¿ya pensaste en aprovechar las sobras, {user_name}? ¡Acá no se tira nada!", "eco", ["Noche", "Casa", "Cocinando"], ["desperdicio_cero", "cocina"]),
|
| 224 |
|
| 225 |
# Noche - Oficina/Estudio
|
| 226 |
+
Nudge("Terminá el día haciendo una lista de lo que lograste, {user_name}. ¡Valorá tu esfuerzo, campeón!", "bienestar", ["Noche", "Oficina/Estudio", "Trabajando"], ["logros", "autoestima"]),
|
| 227 |
+
Nudge("Dejá todo ordenado para mañana, {user_name}. Un buen cierre de día es un buen comienzo del siguiente.", "bienestar", ["Noche", "Oficina/Estudio", "Trabajando"], ["orden", "productividad"]),
|
| 228 |
+
Nudge("Evitá los mails o noticias que te estresen antes de irte, {user_name}. ¡Protegé tu descanso, que es sagrado!", "bienestar", ["Noche", "Oficina/Estudio", "Trabajando"], ["estres", "descanso"]),
|
| 229 |
|
| 230 |
# Noche - Aire Libre/Calle
|
| 231 |
+
Nudge("Si es seguro, mirá las estrellas, {user_name}. Te recuerdan lo inmenso que es todo y lo chiquitos que somos. ¡Bajá un cambio!", "bienestar", ["Noche", "Aire Libre/Calle"], ["estrellas", "reflexion"]),
|
| 232 |
+
Nudge("Escuchá los ruidos de la noche, {user_name}. Un concierto natural para relajar el alma.", "bienestar", ["Noche", "Aire Libre/Calle"], ["sonidos", "calma"]),
|
| 233 |
+
Nudge("Una caminata tranquila antes de volver a casa, {user_name}. Despejá la mente y preparate para el 'descanso del guerrero'.", "bienestar", ["Noche", "Aire Libre/Calle"], ["caminata", "descanso"]),
|
| 234 |
|
| 235 |
# Nudges de Ánimo (para estado_animo_input == "Bajoneado")
|
| 236 |
+
Nudge("¡Arriba ese ánimo, {user_name}! Un matecito y un buen pensamiento pueden cambiar el día. ¡Vos podés!", "bienestar", ["General"], ["animo", "mate"]),
|
| 237 |
+
Nudge("Recordá que hasta el día más nublado tiene un solcito escondido, {user_name}. ¡Fuerza, campeón!", "bienestar", ["General"], ["animo", "esperanza"]),
|
| 238 |
+
Nudge("Date un gusto chiquito hoy, {user_name}. ¡Te lo merecés! Un alfajor, tu música favorita... ¡lo que sea!", "bienestar", ["General"], ["animo", "recompensa"]),
|
| 239 |
+
Nudge("Si te sentís 'bajoneado', un poco de música o una caminata corta pueden ayudar a 'despejar', {user_name}.", "bienestar", ["General"], ["animo", "actividad"]),
|
| 240 |
|
| 241 |
# Nudges para "Modo Quilombo" (activado por contexto social o actividad)
|
| 242 |
+
Nudge("¡Uf, qué día, {user_name}! Respiro hondo. En medio del 'quilombo', una pausa es oro. ¡Tranqui!", "bienestar", ["General", "Quilombo"], ["estres", "respiracion"]),
|
| 243 |
+
Nudge("Si la cosa está 'picada', recordá que no todo depende de vos, {user_name}. Hacé lo que puedas y soltá lo demás.", "bienestar", ["General", "Quilombo"], ["estres", "control"]),
|
| 244 |
+
Nudge("En los días de 'locura', un matecito con calma puede ser tu ancla, {user_name}. ¡A no perder la cabeza!", "bienestar", ["General", "Quilombo"], ["estres", "mate"]),
|
| 245 |
|
| 246 |
# Nudges para "Modo Siesta" (activado por contexto social o actividad)
|
| 247 |
+
Nudge("¡Qué lindo para una siestita, {user_name}! Si podés, aprovechá para recargar energías. ¡Es sagrado!", "bienestar", ["General", "Siesta"], ["descanso", "siesta"]),
|
| 248 |
+
Nudge("El cuerpo te pide un descanso, {user_name}. Escuchalo. Unos minutos de relax pueden hacer la diferencia.", "bienestar", ["General", "Siesta"], ["descanso", "cuerpo"]),
|
| 249 |
|
| 250 |
# Nudges para "Viendo un partido"
|
| 251 |
+
Nudge("¡Vamos Argentina! Disfrutá el partido, {user_name}, pero recordá hidratarte bien. ¡Y si ganamos, a festejar con responsabilidad!", "bienestar", ["General", "Viendo un partido"], ["futbol", "hidratacion", "festejo"]),
|
| 252 |
+
Nudge("El fútbol es pasión, {user_name}, pero no te olvides de estirar un poco si estás mucho tiempo sentado. ¡A mover el esqueleto!", "bienestar", ["General", "Viendo un partido"], ["futbol", "movimiento"]),
|
| 253 |
|
| 254 |
# Nudges de Desafíos MateAI
|
| 255 |
+
Nudge("Desafío MateAI: Hoy, {user_name}, intentá reducir el uso de plásticos de un solo uso. ¡Cada acción cuenta!", "eco", ["Desafio"], ["plastico", "desafio"]),
|
| 256 |
+
Nudge("Desafío MateAI: Dedicá 10 minutos a meditar o simplemente a respirar conscientemente, {user_name}. ¡Tu mente te lo agradecerá!", "bienestar", ["Desafio"], ["meditacion", "desafio"]),
|
| 257 |
+
Nudge("Desafío MateAI: Contactá a un amigo o familiar que hace mucho no ves, {user_name}. ¡Un 'hola' puede alegrar el día!", "bienestar", ["Desafio"], ["social", "desafio"]),
|
| 258 |
+
|
| 259 |
+
# Nudges para Tareas (nuevos)
|
| 260 |
+
Nudge("¡Che {user_name}, no te olvides que tenías pendiente: {task_name}! MateAI te lo recuerda.", "tarea", ["Recordatorio"], ["tarea", "organizacion"]),
|
| 261 |
+
Nudge("Recordá que tenías que {task_name}, {user_name}. ¡Dale que es el momento justo para hacerlo!", "tarea", ["Recordatorio"], ["tarea", "organizacion"]),
|
| 262 |
+
|
| 263 |
+
# Nudges Financieros (nuevos)
|
| 264 |
+
Nudge("Pensá en ese gasto hormiga, {user_name}. Pequeños ahorros suman un montón. ¡Tu bolsillo te lo agradece!", "financiero", ["General"], ["ahorro", "finanzas"]),
|
| 265 |
+
Nudge("¿Ya revisaste tus gastos de la semana, {user_name}? Un poquito de orden financiero trae mucha tranquilidad.", "financiero", ["General"], ["finanzas", "organizacion"]),
|
| 266 |
+
Nudge("Antes de comprar algo grande, {user_name}, pensá si realmente lo necesitás. A veces, menos es más para tu economía.", "financiero", ["General"], ["consumo_consciente", "finanzas"]),
|
| 267 |
]
|
| 268 |
|
| 269 |
ORACULO_REVELATIONS = [
|
| 270 |
+
"La verdadera riqueza no se mide en bienes, sino en la calma del alma y la conexión con el entorno. ¿Qué tesoro descubriste hoy, {user_name}?",
|
| 271 |
+
"El río de la vida fluye constante. No te aferres a la orilla; aprendé a navegar sus corrientes con sabiduría y gratitud, {user_name}.",
|
| 272 |
+
"Cada amanecer es una oportunidad para reescribir tu historia, {user_name}. ¿Qué capítulo nuevo elegís empezar hoy?",
|
| 273 |
+
"La tierra nos susurra secretos ancestrales. Escuchá el viento, sentí el sol, y recordá que sos parte de algo inmenso y sagrado, {user_name}.",
|
| 274 |
+
"El mate compartido es más que una bebida; es un ritual de conexión. ¿Con quién vas a compartir tu energía hoy, {user_name}?",
|
| 275 |
+
"En la simpleza de lo cotidiano reside la magia. Encontrá la belleza en un rayo de sol, en el canto de un pájaro, en una sonrisa, {user_name}.",
|
| 276 |
+
"El viento patagónico te enseña la fuerza, el calor del norte la pasión. En cada rincón de nuestra tierra, una lección. ¿Cuál sentís hoy, {user_name}?",
|
| 277 |
+
"Como el Obelisco en el centro, a veces uno se siente solo en la inmensidad. Recordá que, como él, estás rodeado de historias y vida. ¡Conectate, {user_name}!",
|
| 278 |
+
"La Pacha Mama te abraza en cada paso, {user_name}. Sentí su energía, agradecé su abundancia. ¡Somos parte de ella!",
|
| 279 |
+
"El tango te enseña la melancolía y la pasión. La vida, como el tango, tiene sus pausas y sus arranques. ¿Qué ritmo te toca bailar hoy, {user_name}?"
|
| 280 |
]
|
| 281 |
|
| 282 |
# --- Módulo 5: Motor de Contexto Avanzado (ContextEngine) ---
|
|
|
|
| 296 |
@staticmethod
|
| 297 |
def get_environmental_context():
|
| 298 |
"""Simula datos ambientales (ej. clima, ruido)."""
|
| 299 |
+
climas = ["Soleado", "Nublado", "Lluvioso", "Ventoso", "Fresco", "Caluroso"]
|
| 300 |
return random.choice(climas)
|
| 301 |
|
| 302 |
@staticmethod
|
|
|
|
| 337 |
# Filtrar por tiempo y ubicación (obligatorio)
|
| 338 |
if time_context not in nudge.context_tags:
|
| 339 |
match = False
|
| 340 |
+
if location not in nudge.context_tags and "General" not in nudge.context_tags and "Recordatorio" not in nudge.context_tags: # Tareas pueden ser generales
|
| 341 |
match = False
|
| 342 |
|
| 343 |
# Filtrar por actividad (si es relevante)
|
| 344 |
+
if activity != "Relajado" and activity not in nudge.context_tags and "General" not in nudge.context_tags and "Recordatorio" not in nudge.context_tags:
|
| 345 |
pass # Permitir que pase si es un nudge general
|
| 346 |
|
| 347 |
# Filtrar por estado de ánimo
|
|
|
|
| 375 |
return nudges
|
| 376 |
|
| 377 |
recent_nudges_text = user_history[-5:] # Evitar los últimos 5
|
| 378 |
+
filtered_nudges = [n for n in nudges if n.text_template not in recent_nudges_text] # Usar template para evitar repeticiones
|
| 379 |
return filtered_nudges if filtered_nudges else nudges # Si no hay nuevos, repite (mejor que nada)
|
| 380 |
|
| 381 |
async def generate_nudge(self, user_id, location, activity, user_input_sentiment):
|
| 382 |
user = await self.user_manager.get_user(user_id)
|
| 383 |
if not user:
|
| 384 |
+
# Retorna una tupla completa para todos los outputs esperados por Gradio
|
| 385 |
+
return "Error: Usuario no logueado. Por favor, crea o carga un usuario.", "", "", "", ""
|
| 386 |
|
| 387 |
# Cooldown para evitar spam de nudges
|
| 388 |
if user.last_nudge_time and (datetime.now() - user.last_nudge_time) < user.nudge_cooldown:
|
| 389 |
remaining_time = user.nudge_cooldown - (datetime.now() - user.last_nudge_time)
|
| 390 |
+
# Asegura que todos los outputs esperados por Gradio sean retornados
|
| 391 |
+
current_points = user.eco_points
|
| 392 |
+
current_insignia = gamification_engine.get_insignia(current_points)
|
| 393 |
+
next_insignia_goal = gamification_engine.get_next_insignia_goal(current_points)
|
| 394 |
+
historial = "\n".join(user.nudge_history)
|
| 395 |
+
# El mensaje de cooldown también debe ser vocalizado
|
| 396 |
+
cooldown_msg = f"MateAI necesita un respiro. Volvé a pedir un susurro en {remaining_time.seconds} segundos. ¡La paciencia es una virtud!"
|
| 397 |
+
return cooldown_msg, str(current_points), current_insignia, next_insignia_goal, historial
|
| 398 |
|
| 399 |
# Obtener contexto completo
|
| 400 |
time_context = self.context_engine.get_current_time_context()
|
| 401 |
+
environmental_context = self.context_engine.get_environmental_context()
|
| 402 |
societal_vibe = self.context_engine.get_societal_vibe()
|
| 403 |
sentiment = self.context_engine.get_user_sentiment(activity, user_input_sentiment)
|
| 404 |
|
| 405 |
+
# --- Lógica de Priorización de Tareas (¡NUEVO RAZONAMIENTO!) ---
|
| 406 |
+
# Si hay tareas pendientes y es un buen momento para recordarlas
|
| 407 |
+
tasks_to_nudge = []
|
| 408 |
+
for task_item in user.tasks:
|
| 409 |
+
task_name = task_item['task']
|
| 410 |
+
last_nudged_str = task_item.get('last_nudged')
|
| 411 |
+
last_nudged = datetime.strptime(last_nudged_str, '%Y-%m-%d %H:%M:%S.%f') if last_nudged_str else None
|
| 412 |
+
|
| 413 |
+
if not last_nudged or (datetime.now() - last_nudged) > timedelta(hours=Config.TASK_NUDGE_COOLDOWN_HOURS):
|
| 414 |
+
# Simula un pequeño "razonamiento" sobre cuándo es buen momento para la tarea
|
| 415 |
+
if "Mañana" in time_context and "Casa" in location and "comprar" in task_name.lower():
|
| 416 |
+
tasks_to_nudge.append(task_item)
|
| 417 |
+
elif "Mediodía/Tarde" in time_context and "llamar" in task_name.lower():
|
| 418 |
+
tasks_to_nudge.append(task_item)
|
| 419 |
+
elif "Noche" in time_context and "leer" in task_name.lower():
|
| 420 |
+
tasks_to_nudge.append(task_item)
|
| 421 |
+
elif "General" in task_item.get('context_tags', []): # Tareas sin contexto específico
|
| 422 |
+
tasks_to_nudge.append(task_item)
|
| 423 |
+
|
| 424 |
+
if tasks_to_nudge:
|
| 425 |
+
chosen_task = random.choice(tasks_to_nudge)
|
| 426 |
+
chosen_nudge_template = next(n for n in NUDGE_DATABASE if n.type == "tarea" and "Recordatorio" in n.context_tags)
|
| 427 |
+
formatted_nudge_text = chosen_nudge_template.text_template.format(
|
| 428 |
+
user_name=user.name,
|
| 429 |
+
task_name=chosen_task['task']
|
| 430 |
+
)
|
| 431 |
+
# Actualizar last_nudged para la tarea
|
| 432 |
+
for i, task_item in enumerate(user.tasks):
|
| 433 |
+
if task_item['task'] == chosen_task['task']:
|
| 434 |
+
user.tasks[i]['last_nudged'] = datetime.now().strftime('%Y-%m-%d %H:%M:%S.%f')
|
| 435 |
+
break
|
| 436 |
+
|
| 437 |
+
user.eco_points += Config.ECO_PUNTOS_POR_SUSURRO
|
| 438 |
+
user.nudge_history.append(formatted_nudge_text)
|
| 439 |
+
if len(user.nudge_history) > Config.MAX_HISTORIAL_SUSURROS:
|
| 440 |
+
user.nudge_history.pop(0)
|
| 441 |
+
user.last_nudge_time = datetime.now()
|
| 442 |
+
await self.user_manager.update_user_data(user)
|
| 443 |
+
current_points = user.eco_points
|
| 444 |
+
current_insignia = gamification_engine.get_insignia(current_points)
|
| 445 |
+
next_insignia_goal = gamification_engine.get_next_insignia_goal(current_points)
|
| 446 |
+
historial = "\n".join(user.nudge_history)
|
| 447 |
+
return formatted_nudge_text, str(current_points), current_insignia, next_insignia_goal, historial
|
| 448 |
+
|
| 449 |
+
# --- Si no hay tareas pendientes, proceder con nudges generales ---
|
| 450 |
# Filtrar nudges base
|
| 451 |
possible_nudges = await self._filter_nudges_by_context(
|
| 452 |
NUDGE_DATABASE, time_context, location, activity, societal_vibe, sentiment
|
|
|
|
| 462 |
if user.preferences.get('modo_che_tranqui') or sentiment == "Bajoneado" or "Quilombo" in societal_vibe:
|
| 463 |
calming_nudges = [n for n in possible_nudges if "calma" in n.cultural_tags or "estres" in n.cultural_tags or "respiracion" in n.cultural_tags]
|
| 464 |
if calming_nudges:
|
| 465 |
+
chosen_nudge_template = random.choice(calming_nudges)
|
| 466 |
else:
|
| 467 |
+
chosen_nudge_template = random.choice(possible_nudges) if possible_nudges else Nudge("MateAI está meditando... ¡probá otro contexto!", "reflexivo", ["General"], [])
|
| 468 |
else:
|
| 469 |
+
chosen_nudge_template = random.choice(possible_nudges) if possible_nudges else Nudge("MateAI está meditando... ¡probá otro contexto!", "reflexivo", ["General"], [])
|
| 470 |
|
| 471 |
+
# --- Rellenar la plantilla con contexto dinámico (Simulación de razonamiento) ---
|
| 472 |
+
formatted_nudge_text = chosen_nudge_template.text_template.format(
|
| 473 |
+
user_name=user.name,
|
| 474 |
+
clima_actual=environmental_context,
|
| 475 |
+
ubicacion_actual=location,
|
| 476 |
+
actividad_sugerida=random.choice(["relajarte", "leer un libro", "salir a caminar", "tomar un mate"]) # Ejemplo de sugerencia dinámica
|
| 477 |
+
)
|
| 478 |
+
|
| 479 |
user.eco_points += Config.ECO_PUNTOS_POR_SUSURRO
|
| 480 |
+
user.nudge_history.append(formatted_nudge_text) # Guardar el texto formateado en el historial
|
| 481 |
if len(user.nudge_history) > Config.MAX_HISTORIAL_SUSURROS:
|
| 482 |
user.nudge_history.pop(0)
|
| 483 |
user.last_nudge_time = datetime.now()
|
| 484 |
|
| 485 |
await self.user_manager.update_user_data(user)
|
| 486 |
+
# Asegura que todos los outputs esperados por Gradio sean retornados
|
| 487 |
+
current_points = user.eco_points
|
| 488 |
+
current_insignia = gamification_engine.get_insignia(current_points)
|
| 489 |
+
next_insignia_goal = gamification_engine.get_next_insignia_goal(current_points)
|
| 490 |
+
historial = "\n".join(user.nudge_history)
|
| 491 |
+
return formatted_nudge_text, str(current_points), current_insignia, next_insignia_goal, historial
|
| 492 |
|
| 493 |
async def get_daily_oracle_revelation(self, user_id):
|
| 494 |
user = await self.user_manager.get_user(user_id)
|
|
|
|
| 499 |
if user.last_oracle_date and user.last_oracle_date == today:
|
| 500 |
return "El Oráculo ya te ha hablado hoy. Volvé mañana para una nueva revelación."
|
| 501 |
|
| 502 |
+
revelation_template = random.choice(ORACULO_REVELATIONS)
|
| 503 |
+
formatted_revelation = revelation_template.format(user_name=user.name) # Personalizar la revelación
|
| 504 |
user.last_oracle_date = today
|
| 505 |
await self.user_manager.update_user_data(user)
|
| 506 |
+
return formatted_revelation
|
| 507 |
|
| 508 |
async def get_mateai_challenge(self, user_id):
|
| 509 |
user = await self.user_manager.get_user(user_id)
|
|
|
|
| 514 |
if not challenge_nudges:
|
| 515 |
return "MateAI está pensando en un desafío épico... ¡Volvé pronto!"
|
| 516 |
|
| 517 |
+
chosen_challenge_template = random.choice(challenge_nudges)
|
| 518 |
+
formatted_challenge = chosen_challenge_template.text_template.format(user_name=user.name)
|
| 519 |
+
return formatted_challenge
|
| 520 |
+
|
| 521 |
+
async def add_task(self, user_id, task_name):
|
| 522 |
+
user = await self.user_manager.get_user(user_id)
|
| 523 |
+
if not user:
|
| 524 |
+
return "Error: Usuario no logueado. Por favor, crea o carga un usuario."
|
| 525 |
+
|
| 526 |
+
new_task = {"task": task_name, "added_at": datetime.now().strftime('%Y-%m-%d %H:%M:%S.%f'), "last_nudged": None}
|
| 527 |
+
user.tasks.append(new_task)
|
| 528 |
+
await self.user_manager.update_user_data(user)
|
| 529 |
+
return f"¡Tarea '{task_name}' agregada a tu lista, {user.name}!"
|
| 530 |
+
|
| 531 |
+
async def complete_task(self, user_id, task_name):
|
| 532 |
+
user = await self.user_manager.get_user(user_id)
|
| 533 |
+
if not user:
|
| 534 |
+
return "Error: Usuario no logueado. Por favor, crea o carga un usuario."
|
| 535 |
+
|
| 536 |
+
initial_task_count = len(user.tasks)
|
| 537 |
+
user.tasks = [task for task in user.tasks if task['task'].lower() != task_name.lower()]
|
| 538 |
+
|
| 539 |
+
if len(user.tasks) < initial_task_count:
|
| 540 |
+
await self.user_manager.update_user_data(user)
|
| 541 |
+
return f"¡Tarea '{task_name}' marcada como completada, {user.name}! ¡Bien ahí!"
|
| 542 |
+
return f"No encontré la tarea '{task_name}' en tu lista, {user.name}."
|
| 543 |
|
| 544 |
# --- Módulo 7: Motor de Gamificación (GamificationEngine) ---
|
| 545 |
class GamificationEngine:
|
|
|
|
| 577 |
nudge_generator = NudgeGenerator(user_manager, context_engine)
|
| 578 |
gamification_engine = GamificationEngine() # No necesita instanciar, es de clase
|
| 579 |
|
| 580 |
+
# --- Módulo 8: Interfaz Gradio (UI/UX Argento de Nivel Superior con Voz) ---
|
| 581 |
with gr.Blocks(theme=gr.themes.Soft(), css="footer { display: none !important; }") as demo:
|
| 582 |
+
# gr.State para mantener el objeto User en la sesión
|
| 583 |
+
current_user_state = gr.State(None)
|
| 584 |
+
|
| 585 |
gr.Markdown(
|
| 586 |
"""
|
| 587 |
<h1 style="text-align: center; color: #10b981; font-size: 3.5em; font-weight: bold; margin-bottom: 0.5em; text-shadow: 2px 2px 4px rgba(0,0,0,0.1);">
|
| 588 |
+
🧉 MateAI: El Oráculo Vocal y Adaptativo del Bienestar Argento 🧉
|
| 589 |
</h1>
|
| 590 |
<p style="text-align: center; color: #4b5563; font-size: 1.3em; margin-bottom: 2em; line-height: 1.5;">
|
| 591 |
Una inteligencia superior diseñada para cebarte la vida con sabiduría contextual y sin costo.
|
|
|
|
| 611 |
border: 6px solid #10b981; /* emerald-600 */
|
| 612 |
text-shadow: 1px 1px 3px rgba(0,0,0,0.3);
|
| 613 |
">
|
| 614 |
+
<span style="font-size: 0.8em;">✨ Oráculo Vocal ✨</span>
|
| 615 |
<span>🇦🇷 MateAI 🇦🇷</span>
|
| 616 |
</div>
|
| 617 |
<style>
|
|
|
|
| 625 |
"""
|
| 626 |
)
|
| 627 |
|
| 628 |
+
# --- Componentes de voz ocultos para la interacción JS ---
|
| 629 |
+
gr.HTML("""
|
| 630 |
+
<script>
|
| 631 |
+
let recognition;
|
| 632 |
+
let speaking = false;
|
| 633 |
+
let lastSpokenText = "";
|
| 634 |
+
|
| 635 |
+
// Función para inicializar y controlar el reconocimiento de voz
|
| 636 |
+
function startListening() {
|
| 637 |
+
if (recognition) {
|
| 638 |
+
recognition.stop();
|
| 639 |
+
}
|
| 640 |
+
recognition = new (window.SpeechRecognition || window.webkitSpeechRecognition)();
|
| 641 |
+
recognition.lang = 'es-AR'; // Español de Argentina
|
| 642 |
+
recognition.interimResults = false;
|
| 643 |
+
recognition.maxAlternatives = 1;
|
| 644 |
+
|
| 645 |
+
recognition.onstart = function() {
|
| 646 |
+
document.getElementById('voice_status').textContent = 'Escuchando... 🎙️';
|
| 647 |
+
document.getElementById('voice_status').style.color = '#38bdf8'; // Sky blue
|
| 648 |
+
console.log('Voice recognition started.');
|
| 649 |
+
};
|
| 650 |
+
|
| 651 |
+
recognition.onresult = function(event) {
|
| 652 |
+
const transcript = event.results[0][0].transcript;
|
| 653 |
+
document.getElementById('voice_input_textbox').value = transcript; // Pasa el texto al textbox oculto
|
| 654 |
+
document.getElementById('voice_status').textContent = 'Texto reconocido: ' + transcript;
|
| 655 |
+
console.log('Recognized:', transcript);
|
| 656 |
+
// Trigger a Gradio event to send this text to Python
|
| 657 |
+
// This will simulate a click on a hidden button or direct input
|
| 658 |
+
document.getElementById('hidden_voice_submit_button').click();
|
| 659 |
+
};
|
| 660 |
+
|
| 661 |
+
recognition.onend = function() {
|
| 662 |
+
if (!speaking) { // Solo si no está hablando MateAI
|
| 663 |
+
document.getElementById('voice_status').textContent = 'Listo para hablar 🎤';
|
| 664 |
+
document.getElementById('voice_status').style.color = '#4b5563'; // Gray
|
| 665 |
+
}
|
| 666 |
+
console.log('Voice recognition ended.');
|
| 667 |
+
};
|
| 668 |
+
|
| 669 |
+
recognition.onerror = function(event) {
|
| 670 |
+
console.error('Speech recognition error:', event.error);
|
| 671 |
+
document.getElementById('voice_status').textContent = 'Error de voz: ' + event.error + ' ❌';
|
| 672 |
+
document.getElementById('voice_status').style.color = '#ef4444'; // Red
|
| 673 |
+
if (event.error === 'not-allowed' || event.error === 'permission-denied') {
|
| 674 |
+
alert('Por favor, permite el acceso al micrófono para usar la voz.');
|
| 675 |
+
}
|
| 676 |
+
};
|
| 677 |
+
|
| 678 |
+
recognition.start();
|
| 679 |
+
}
|
| 680 |
+
|
| 681 |
+
// Función para que MateAI hable
|
| 682 |
+
function speakText(text) {
|
| 683 |
+
if (!text || text === lastSpokenText) return; // Evita repetir el mismo texto
|
| 684 |
+
lastSpokenText = text;
|
| 685 |
+
|
| 686 |
+
const utterance = new SpeechSynthesisUtterance(text);
|
| 687 |
+
utterance.lang = 'es-AR'; // Español de Argentina
|
| 688 |
+
|
| 689 |
+
// Intenta encontrar una voz en español de Argentina o una genérica de español
|
| 690 |
+
const voices = window.speechSynthesis.getVoices();
|
| 691 |
+
const preferredVoice = voices.find(voice => voice.lang === 'es-AR') ||
|
| 692 |
+
voices.find(voice => voice.lang.startsWith('es'));
|
| 693 |
+
if (preferredVoice) {
|
| 694 |
+
utterance.voice = preferredVoice;
|
| 695 |
+
} else {
|
| 696 |
+
console.warn("No se encontró una voz en español de Argentina o genérica. Usando la voz por defecto.");
|
| 697 |
+
}
|
| 698 |
+
|
| 699 |
+
utterance.onstart = function() {
|
| 700 |
+
speaking = true;
|
| 701 |
+
document.getElementById('voice_status').textContent = 'MateAI hablando... 🔊';
|
| 702 |
+
document.getElementById('voice_status').style.color = '#10b981'; // Emerald green
|
| 703 |
+
};
|
| 704 |
+
|
| 705 |
+
utterance.onend = function() {
|
| 706 |
+
speaking = false;
|
| 707 |
+
document.getElementById('voice_status').textContent = 'Listo para hablar 🎤';
|
| 708 |
+
document.getElementById('voice_status').style.color = '#4b5563'; // Gray
|
| 709 |
+
// Si el reconocimiento estaba activo antes de hablar, reiniciarlo
|
| 710 |
+
if (recognition && recognition.continuous) { // Si se desea escucha continua después de hablar
|
| 711 |
+
recognition.start();
|
| 712 |
+
}
|
| 713 |
+
};
|
| 714 |
+
|
| 715 |
+
utterance.onerror = function(event) {
|
| 716 |
+
speaking = false;
|
| 717 |
+
console.error('Speech synthesis error:', event.error);
|
| 718 |
+
document.getElementById('voice_status').textContent = 'Error al hablar 🔇';
|
| 719 |
+
document.getElementById('voice_status').style.color = '#ef4444'; // Red
|
| 720 |
+
};
|
| 721 |
+
|
| 722 |
+
window.speechSynthesis.speak(utterance);
|
| 723 |
+
}
|
| 724 |
+
|
| 725 |
+
// Exponer la función speakText globalmente para que Gradio pueda llamarla
|
| 726 |
+
window.speakText = speakText;
|
| 727 |
+
</script>
|
| 728 |
+
""")
|
| 729 |
+
# Input oculto para el texto reconocido por voz
|
| 730 |
+
voice_input_textbox = gr.Textbox(elem_id="voice_input_textbox", visible=False)
|
| 731 |
+
# Botón oculto para enviar el texto reconocido al backend de Gradio
|
| 732 |
+
hidden_voice_submit_button = gr.Button("Submit Voice Input", elem_id="hidden_voice_submit_button", visible=False)
|
| 733 |
+
# Output oculto para el texto que MateAI debe vocalizar
|
| 734 |
+
voice_output_text = gr.Textbox(elem_id="voice_output_text", visible=False)
|
| 735 |
+
|
| 736 |
+
gr.Markdown("<p id='voice_status' style='text-align: center; font-weight: bold; color: #4b5563;'>Listo para hablar 🎤</p>")
|
| 737 |
+
gr.Button("🎙️ Empezar a Hablar con MateAI 🎙️", variant="secondary", elem_id="start_voice_button").click(
|
| 738 |
+
fn=None,
|
| 739 |
+
inputs=None,
|
| 740 |
+
outputs=None,
|
| 741 |
+
js="startListening()"
|
| 742 |
+
)
|
| 743 |
+
|
| 744 |
# --- Tab 1: Inicio y Perfil ---
|
| 745 |
with gr.Tab("Inicio & Perfil"):
|
| 746 |
gr.Markdown("<h2 style='color: #10b981;'>¡Bienvenido a tu Espacio MateAI!</h2><p>Acá manejás tu perfil para que MateAI te conozca a fondo.</p>")
|
|
|
|
| 861 |
btn_descargar_diario = gr.Button("Descargar Diario (sesión actual)", variant="secondary")
|
| 862 |
gr.Markdown("<p style='font-size: 0.9em; color: #6b7280; margin-top: 1em;'><i>Nota: El diario se descarga como texto. Para guardar permanentemente, deberías copiarlo o integrarlo con otro servicio.</i></p>")
|
| 863 |
|
| 864 |
+
# --- Tab 6: Mi Gestor de Tareas MateAI ---
|
| 865 |
+
with gr.Tab("Mi Gestor de Tareas MateAI"):
|
| 866 |
+
gr.Markdown("<h2 style='color: #10b981;'>📝 Tus Tareas con MateAI 📝</h2><p>Agregá lo que tenés que hacer y MateAI te lo recordará en el momento justo.</p>")
|
| 867 |
+
tarea_nueva_input = gr.Textbox(label="Nueva Tarea", placeholder="Ej: Comprar yerba, Llamar a mi vieja, Leer un libro")
|
| 868 |
+
btn_agregar_tarea = gr.Button("Agregar Tarea", variant="primary")
|
| 869 |
+
output_agregar_tarea = gr.Textbox(label="Estado de la Tarea", interactive=False)
|
| 870 |
+
|
| 871 |
+
gr.Markdown("<h3 style='color: #38bdf8; margin-top: 1.5em;'>Tus Tareas Pendientes</h3>")
|
| 872 |
+
tareas_pendientes_output = gr.Textbox(label="Lista de Tareas", lines=5, interactive=False)
|
| 873 |
+
|
| 874 |
+
tarea_completar_input = gr.Textbox(label="Marcar Tarea como Completada", placeholder="Ej: Comprar yerba")
|
| 875 |
+
btn_completar_tarea = gr.Button("Completar Tarea", variant="secondary")
|
| 876 |
+
output_completar_tarea = gr.Textbox(label="Estado de Completado", interactive=False)
|
| 877 |
+
|
| 878 |
# --- Funciones de Interacción para Gradio ---
|
| 879 |
|
| 880 |
+
async def _update_ui_from_user(user_obj):
|
| 881 |
+
"""Función auxiliar para actualizar todos los campos de la UI desde un objeto User."""
|
| 882 |
+
if user_obj:
|
| 883 |
+
current_points = user_obj.eco_points
|
| 884 |
+
current_insignia = gamification_engine.get_insignia(current_points)
|
| 885 |
+
next_insignia_goal = gamification_engine.get_next_insignia_goal(current_points)
|
| 886 |
+
historial = "\n".join(user_obj.nudge_history)
|
| 887 |
+
tareas_str = "\n".join([f"- {t['task']} (Agregada el: {datetime.strptime(t['added_at'], '%Y-%m-%d %H:%M:%S.%f').strftime('%d/%m')})" for t in user_obj.tasks])
|
| 888 |
+
|
| 889 |
+
return (user_obj, user_obj.user_id, user_obj.name, str(current_points), current_insignia, next_insignia_goal,
|
| 890 |
+
historial, user_obj.preferences.get('tipo_susurro'), user_obj.preferences.get('frecuencia'),
|
| 891 |
+
user_obj.preferences.get('modo_che_tranqui'), tareas_str)
|
| 892 |
+
|
| 893 |
+
# Valores por defecto si no hay usuario
|
| 894 |
+
return (None, "No logueado", "", "", "", "", "", Config.DEFAULT_USER_PREFS['tipo_susurro'],
|
| 895 |
+
Config.DEFAULT_USER_PREFS['frecuencia'], Config.DEFAULT_USER_PREFS['modo_che_tranqui'], "")
|
| 896 |
+
|
| 897 |
async def _create_user_gradio(name, prefs_type):
|
| 898 |
user, msg = await user_manager.create_user(name, {"tipo_susurro": prefs_type})
|
| 899 |
if user:
|
| 900 |
+
# Actualiza todos los outputs de UI y el estado del usuario
|
| 901 |
+
ui_outputs = await _update_ui_from_user(user)
|
| 902 |
+
return (msg, *ui_outputs) # Devuelve el mensaje y luego el resto de los outputs
|
| 903 |
+
return msg, None, "No logueado", "", "", "", "", "", Config.DEFAULT_USER_PREFS['tipo_susurro'], Config.DEFAULT_USER_PREFS['frecuencia'], Config.DEFAULT_USER_PREFS['modo_che_tranqui'], ""
|
|
|
|
| 904 |
|
| 905 |
async def _load_user_gradio(user_id):
|
| 906 |
user, msg = await user_manager.login_user(user_id)
|
| 907 |
if user:
|
| 908 |
+
ui_outputs = await _update_ui_from_user(user)
|
| 909 |
+
return (msg, *ui_outputs)
|
| 910 |
+
return msg, None, "No logueado", "", "", "", "", "", Config.DEFAULT_USER_PREFS['tipo_susurro'], Config.DEFAULT_USER_PREFS['frecuencia'], Config.DEFAULT_USER_PREFS['modo_che_tranqui'], ""
|
|
|
|
|
|
|
| 911 |
|
| 912 |
+
async def _update_prefs_gradio(user_obj, tipo_susurro, frecuencia, modo_che_tranqui):
|
| 913 |
+
if not user_obj:
|
| 914 |
+
return "Error: Por favor, crea o carga un usuario primero.", user_obj
|
|
|
|
| 915 |
|
| 916 |
+
user_obj.preferences.update({
|
| 917 |
"tipo_susurro": tipo_susurro,
|
| 918 |
"frecuencia": frecuencia,
|
| 919 |
"modo_che_tranqui": modo_che_tranqui
|
| 920 |
+
})
|
| 921 |
+
success = await user_manager.update_user_data(user_obj)
|
|
|
|
| 922 |
if success:
|
| 923 |
+
return f"Preferencias actualizadas para {user_obj.name}.", user_obj
|
| 924 |
+
return "Error al actualizar preferencias.", user_obj
|
| 925 |
|
| 926 |
+
async def _generate_nudge_gradio(user_obj, location, activity, sentiment):
|
| 927 |
+
if not user_obj:
|
| 928 |
+
return "Por favor, crea o carga un usuario primero para recibir susurros personalizados.", "", "", "", "", ""
|
| 929 |
|
| 930 |
+
nudge_text = await nudge_generator.generate_nudge(user_obj.user_id, location, activity, sentiment)
|
| 931 |
|
| 932 |
+
# Recargar el objeto de usuario para asegurar que los puntos e historial estén actualizados
|
| 933 |
+
updated_user = await user_manager.get_user(user_obj.user_id)
|
| 934 |
+
|
| 935 |
+
current_points = updated_user.eco_points
|
| 936 |
current_insignia = gamification_engine.get_insignia(current_points)
|
| 937 |
next_insignia_goal = gamification_engine.get_next_insignia_goal(current_points)
|
| 938 |
+
historial = "\n".join(updated_user.nudge_history)
|
| 939 |
|
| 940 |
+
return nudge_text, str(current_points), current_insignia, next_insignia_goal, historial, nudge_text # Retorna el texto para vocalizar
|
| 941 |
+
|
| 942 |
+
async def _get_oracle_revelation_gradio(user_obj):
|
| 943 |
+
if not user_obj:
|
| 944 |
+
return "Por favor, crea o carga un usuario primero para consultar al Oráculo.", ""
|
| 945 |
+
revelation_text = await nudge_generator.get_daily_oracle_revelation(user_obj.user_id)
|
| 946 |
+
return revelation_text, revelation_text # Retorna el texto para vocalizar
|
| 947 |
+
|
| 948 |
+
async def _get_mateai_challenge_gradio(user_obj):
|
| 949 |
+
if not user_obj:
|
| 950 |
+
return "Por favor, crea o carga un usuario primero para recibir desafíos.", ""
|
| 951 |
+
challenge_text = await nudge_generator.get_mateai_challenge(user_obj.user_id)
|
| 952 |
+
return challenge_text, challenge_text # Retorna el texto para vocalizar
|
| 953 |
+
|
| 954 |
+
async def _add_task_gradio(user_obj, task_name):
|
| 955 |
+
if not user_obj:
|
| 956 |
+
return "Error: Por favor, crea o carga un usuario primero.", ""
|
| 957 |
+
msg = await nudge_generator.add_task(user_obj.user_id, task_name)
|
| 958 |
+
updated_user = await user_manager.get_user(user_obj.user_id)
|
| 959 |
+
tareas_str = "\n".join([f"- {t['task']} (Agregada el: {datetime.strptime(t['added_at'], '%Y-%m-%d %H:%M:%S.%f').strftime('%d/%m')})" for t in updated_user.tasks])
|
| 960 |
+
return msg, tareas_str
|
| 961 |
+
|
| 962 |
+
async def _complete_task_gradio(user_obj, task_name):
|
| 963 |
+
if not user_obj:
|
| 964 |
+
return "Error: Por favor, crea o carga un usuario primero.", ""
|
| 965 |
+
msg = await nudge_generator.complete_task(user_obj.user_id, task_name)
|
| 966 |
+
updated_user = await user_manager.get_user(user_obj.user_id)
|
| 967 |
+
tareas_str = "\n".join([f"- {t['task']} (Agregada el: {datetime.strptime(t['added_at'], '%Y-%m-%d %H:%M:%S.%f').strftime('%d/%m')})" for t in updated_user.tasks])
|
| 968 |
+
return msg, tareas_str
|
| 969 |
|
| 970 |
def _download_diary_gradio(diary_text):
|
| 971 |
return gr.File(value=diary_text.encode('utf-8'), filename="diario_mateai.txt", type="bytes")
|
|
|
|
| 974 |
btn_crear_usuario.click(
|
| 975 |
fn=_create_user_gradio,
|
| 976 |
inputs=[nombre_nuevo_usuario, preferencias_iniciales],
|
| 977 |
+
outputs=[current_user_state, usuario_actual_id, output_creacion_usuario, usuario_actual_nombre, usuario_actual_puntos, usuario_actual_insignia, usuario_proxima_insignia, historial_susurros_output, nuevas_preferencias_tipo, nuevas_preferencias_frecuencia, modo_che_tranqui_checkbox, tareas_pendientes_output]
|
| 978 |
)
|
| 979 |
|
| 980 |
btn_cargar_perfil.click(
|
| 981 |
fn=_load_user_gradio,
|
| 982 |
inputs=[user_id_existente],
|
| 983 |
+
outputs=[current_user_state, usuario_actual_id, output_cargar_perfil, usuario_actual_nombre, usuario_actual_puntos, usuario_actual_insignia, usuario_proxima_insignia, historial_susurros_output, nuevas_preferencias_tipo, nuevas_preferencias_frecuencia, modo_che_tranqui_checkbox, tareas_pendientes_output]
|
| 984 |
)
|
| 985 |
|
| 986 |
btn_actualizar_preferencias.click(
|
| 987 |
fn=_update_prefs_gradio,
|
| 988 |
+
inputs=[current_user_state, nuevas_preferencias_tipo, nuevas_preferencias_frecuencia, modo_che_tranqui_checkbox],
|
| 989 |
+
outputs=[output_actualizar_preferencias, current_user_state]
|
| 990 |
)
|
| 991 |
|
| 992 |
btn_generar_susurro.click(
|
| 993 |
fn=_generate_nudge_gradio,
|
| 994 |
+
inputs=[current_user_state, ubicacion_input, actividad_input, estado_animo_input],
|
| 995 |
+
outputs=[susurro_output, usuario_actual_puntos, usuario_actual_insignia, usuario_proxima_insignia, historial_susurros_output, voice_output_text]
|
| 996 |
)
|
| 997 |
|
| 998 |
btn_oraculo.click(
|
| 999 |
fn=_get_oracle_revelation_gradio,
|
| 1000 |
+
inputs=[current_user_state],
|
| 1001 |
+
outputs=[oraculo_output, voice_output_text]
|
| 1002 |
)
|
| 1003 |
|
| 1004 |
btn_desafio.click(
|
| 1005 |
fn=_get_mateai_challenge_gradio,
|
| 1006 |
+
inputs=[current_user_state],
|
| 1007 |
+
outputs=[desafio_output, voice_output_text]
|
| 1008 |
+
)
|
| 1009 |
+
|
| 1010 |
+
btn_agregar_tarea.click(
|
| 1011 |
+
fn=_add_task_gradio,
|
| 1012 |
+
inputs=[current_user_state, tarea_nueva_input],
|
| 1013 |
+
outputs=[output_agregar_tarea, tareas_pendientes_output]
|
| 1014 |
+
)
|
| 1015 |
+
|
| 1016 |
+
btn_completar_tarea.click(
|
| 1017 |
+
fn=_complete_task_gradio,
|
| 1018 |
+
inputs=[current_user_state, tarea_completar_input],
|
| 1019 |
+
outputs=[output_completar_tarea, tareas_pendientes_output]
|
| 1020 |
)
|
| 1021 |
|
| 1022 |
btn_descargar_diario.click(
|
|
|
|
| 1025 |
outputs=gr.File(label="Descargar tu Diario")
|
| 1026 |
)
|
| 1027 |
|
| 1028 |
+
# --- Manejo de la entrada de voz ---
|
| 1029 |
+
# Cuando el texto es reconocido por JS y puesto en voice_input_textbox,
|
| 1030 |
+
# este evento lo envía al backend para procesamiento.
|
| 1031 |
+
# NOTA: En una implementación completa de comandos de voz, se necesitaría un parser de lenguaje natural
|
| 1032 |
+
# para mapear el texto reconocido a las funciones de Gradio. Aquí, se muestra un mensaje de ejemplo.
|
| 1033 |
+
hidden_voice_submit_button.click(
|
| 1034 |
+
fn=lambda text: f"Comando de voz recibido: '{text}'. Por favor, usa los botones o campos para interactuar directamente. ¡MateAI aún está aprendiendo a entender tus comandos hablados complejos!", # Mensaje temporal
|
| 1035 |
+
inputs=[voice_input_textbox],
|
| 1036 |
+
outputs=[susurro_output] # Solo para mostrar que se recibió el comando
|
| 1037 |
+
)
|
| 1038 |
+
|
| 1039 |
+
# Cuando el backend genera texto para voz, lo envía a voice_output_text,
|
| 1040 |
+
# y el JS lo vocaliza.
|
| 1041 |
+
voice_output_text.change(
|
| 1042 |
+
fn=None, # No hay función Python, solo se usa para activar JS
|
| 1043 |
+
inputs=[voice_output_text],
|
| 1044 |
+
outputs=None,
|
| 1045 |
+
js="text => window.speakText(text)"
|
| 1046 |
+
)
|