Lukeetah commited on
Commit
185c347
·
verified ·
1 Parent(s): eadeba9

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +256 -211
app.py CHANGED
@@ -10,17 +10,13 @@ import asyncio # Para manejar operaciones asíncronas
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
@@ -28,51 +24,63 @@ class Config:
28
  TASK_NUDGE_COOLDOWN_HOURS = 4 # Cooldown para recordar la misma tarea
29
 
30
  # --- Firebase Configuration ---
31
- # Para desplegar en Hugging Face Spaces:
32
- # 1. Crea un proyecto en Firebase y habilita Firestore.
33
- # 2. Ve a 'Configuración del proyecto' -> 'Cuentas de servicio' -> 'Generar nueva clave privada'.
34
- # Esto descargará un archivo JSON.
35
- # 3. En tu Space de Hugging Face, ve a 'Settings' -> 'Repository secrets'.
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.
43
- # En Hugging Face Spaces, las credenciales se cargarán desde GOOGLE_APPLICATION_CREDENTIALS_JSON.
44
  try:
45
- firebase_admin.get_app() # Verifica si la app ya está inicializada
46
- except ValueError:
47
- # Intenta cargar las credenciales desde la variable de entorno
48
- # 'GOOGLE_APPLICATION_CREDENTIALS_JSON' que debe contener el JSON de la clave de servicio.
49
- firebase_credentials_json = os.getenv('GOOGLE_APPLICATION_CREDENTIALS_JSON')
50
- if firebase_credentials_json:
51
- try:
52
- cred = credentials.Certificate(json.loads(firebase_credentials_json))
53
  firebase_admin.initialize_app(cred)
54
  print("Firebase Admin SDK inicializado correctamente desde variable de entorno.")
55
- except Exception as e:
56
- print(f"ERROR: No se pudo inicializar Firebase Admin SDK desde GOOGLE_APPLICATION_CREDENTIALS_JSON: {e}")
57
- print("Asegúrate de que la variable de entorno esté configurada correctamente.")
58
- else:
59
- print("ADVERTENCIA: GOOGLE_APPLICATION_CREDENTIALS_JSON no está configurada.")
60
- print("La persistencia de datos de usuario en Firestore NO funcionará sin credenciales.")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
61
 
62
- db = firestore.client()
63
 
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
70
  prefs = initial_prefs if initial_prefs else Config.DEFAULT_USER_PREFS.copy()
71
  new_user = User(user_id, name, prefs)
72
 
73
  user_doc_ref = db.collection(Config.FIREBASE_COLLECTION_USERS).document(user_id)
74
  try:
75
- # Removed 'await' here as firestore.client().collection().document().set() is synchronous
76
  user_doc_ref.set(new_user.to_dict())
77
  return new_user, f"¡Bienvenido, {name}! Tu ID de usuario es: {user_id}. ¡MateAI está listo para cebarte la vida!"
78
  except Exception as e:
@@ -80,9 +88,9 @@ class UserManager:
80
 
81
  @classmethod
82
  async def login_user(cls, user_id):
 
83
  user_doc_ref = db.collection(Config.FIREBASE_COLLECTION_USERS).document(user_id)
84
  try:
85
- # Firestore get() is synchronous
86
  doc = user_doc_ref.get()
87
  if doc.exists:
88
  user_data = doc.to_dict()
@@ -103,9 +111,9 @@ class UserManager:
103
 
104
  @classmethod
105
  async def get_user(cls, user_id):
 
106
  user_doc_ref = db.collection(Config.FIREBASE_COLLECTION_USERS).document(user_id)
107
  try:
108
- # Firestore get() is synchronous
109
  doc = user_doc_ref.get()
110
  if doc.exists:
111
  user_data = doc.to_dict()
@@ -126,9 +134,9 @@ class UserManager:
126
 
127
  @classmethod
128
  async def update_user_data(cls, user_obj):
 
129
  user_doc_ref = db.collection(Config.FIREBASE_COLLECTION_USERS).document(user_obj.user_id)
130
  try:
131
- # Removed 'await' here as firestore.client().collection().document().update() is synchronous
132
  user_doc_ref.update(user_obj.to_dict())
133
  return True
134
  except Exception as e:
@@ -500,7 +508,6 @@ class NudgeGenerator:
500
  return "Error: Usuario no logueado. Por favor, crea o carga un usuario."
501
 
502
  today = datetime.now().date()
503
- # Firestore get() is synchronous
504
  if user.last_oracle_date and user.last_oracle_date == today:
505
  return "El Oráculo ya te ha hablado hoy. Volvé mañana para una nueva revelación."
506
 
@@ -524,8 +531,8 @@ class NudgeGenerator:
524
  return formatted_challenge
525
 
526
  async def add_task(self, user_id, task_name):
527
- if not user_id: # Check if user_id is None or empty
528
- return "Error: Por favor, crea o carga un usuario primero.", "" # Return default values for outputs
529
  user = await self.user_manager.get_user(user_id)
530
  if not user:
531
  return "Error: Usuario no logueado. Por favor, crea o carga un usuario.", ""
@@ -537,8 +544,8 @@ class NudgeGenerator:
537
  return f"¡Tarea '{task_name}' agregada a tu lista, {user.name}!", tareas_str
538
 
539
  async def complete_task(self, user_id, task_name):
540
- if not user_id: # Check if user_id is None or empty
541
- return "Error: Por favor, crea o carga un usuario primero.", "" # Return default values for outputs
542
  user = await self.user_manager.get_user(user_id)
543
  if not user:
544
  return "Error: Usuario no logueado. Por favor, crea o carga un usuario.", ""
@@ -603,7 +610,7 @@ with demo: # Ahora el bloque 'with' utiliza el objeto demo ya creado
603
  gr.Markdown(
604
  """
605
  <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);">
606
- 🧉 MateAI: El Oráculo Vocal y Adaptativo del Bienestar Argento 🧉
607
  </h1>
608
  <p style="text-align: center; color: #4b5563; font-size: 1.3em; margin-bottom: 2em; line-height: 1.5;">
609
  Soy MateAI, tu compañero argentino. Estoy acá para cebarte la vida con sabiduría contextual y sin costo.
@@ -690,7 +697,7 @@ with demo: # Ahora el bloque 'with' utiliza el objeto demo ya creado
690
  border-radius: 20px;
691
  box-shadow: 0 8px 20px rgba(0,0,0,0.1);
692
  }
693
- .menu-button {
694
  display: flex;
695
  flex-direction: column;
696
  align-items: center;
@@ -707,13 +714,13 @@ with demo: # Ahora el bloque 'with' utiliza el objeto demo ya creado
707
  text-align: center;
708
  padding: 10px;
709
  }
710
- .menu-button:hover {
711
  background-color: #e0f2fe; /* sky-100 */
712
  border-color: #38bdf8; /* sky-400 */
713
  transform: translateY(-3px);
714
  box-shadow: 0 4px 10px rgba(0,0,0,0.1);
715
  }
716
- .menu-button .icon {
717
  font-size: 2.5em; /* Tamaño del icono */
718
  margin-bottom: 5px;
719
  }
@@ -724,6 +731,41 @@ with demo: # Ahora el bloque 'with' utiliza el objeto demo ya creado
724
  box-shadow: 0 8px 20px rgba(0,0,0,0.1);
725
  margin-top: 20px;
726
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
727
  </style>
728
  <div id="matecito_avatar" class="matecito-avatar">🧉</div>
729
  <script>
@@ -786,7 +828,7 @@ with demo: # Ahora el bloque 'with' utiliza el objeto demo ya creado
786
  voiceStatus.textContent = 'No se detectó voz. Intenta hablar más claro o más fuerte. 🎤';
787
  voiceStatus.style.color = '#fbbf24'; // Amber
788
  } else if (event.error === 'aborted') {
789
- voiceStatus.textContent = 'Reconocimiento de voz cancelado. ';
790
  voiceStatus.style.color = '#4b5563';
791
  }
792
  };
@@ -802,18 +844,20 @@ with demo: # Ahora el bloque 'with' utiliza el objeto demo ya creado
802
  const utterance = new SpeechSynthesisUtterance(text);
803
  utterance.lang = 'es-AR'; // Español de Argentina
804
 
805
- // Intenta encontrar una voz en español de Argentina o una genérica de español
806
  const voices = window.speechSynthesis.getVoices();
807
- // Prioriza voces de Argentina, luego cualquier español, luego la por defecto
808
- // Se busca una voz que suene más "argentina" por su nombre o lang code
809
- const preferredVoice = voices.find(voice => voice.lang === 'es-AR' && (voice.name.includes('Argentina') || voice.name.includes('Diego') || voice.name.includes('Laura') || voice.name.includes('Argentine') || voice.name.includes('male'))) ||
810
- voices.find(voice => voice.lang === 'es-AR') ||
811
- voices.find(voice => voice.lang.startsWith('es') && voice.name.includes('male')) ||
812
- voices.find(voice => voice.lang.startsWith('es'));
 
 
813
  if (preferredVoice) {
814
  utterance.voice = preferredVoice;
815
  } else {
816
- console.warn("No se encontró una voz en español de Argentina o genérica. Usando la voz por defecto.");
817
  }
818
 
819
  utterance.onstart = function() {
@@ -842,7 +886,7 @@ with demo: # Ahora el bloque 'with' utiliza el objeto demo ya creado
842
  utterance.onerror = function(event) {
843
  speaking = false;
844
  console.error('Speech synthesis error:', event.error);
845
- voiceStatus.textContent = 'Error al hablar 🔇';
846
  voiceStatus.style.color = '#ef4444'; // Red
847
  matecitoAvatar.classList.remove('matecito-talking');
848
  matecitoAvatar.style.animation = 'fade-out 0.3s forwards';
@@ -854,6 +898,17 @@ with demo: # Ahora el bloque 'with' utiliza el objeto demo ya creado
854
 
855
  // Exponer la función speakText globalmente para que Gradio pueda llamarla
856
  window.speakText = speakText;
 
 
 
 
 
 
 
 
 
 
 
857
  </script>
858
  """)
859
  # Input oculto para el texto reconocido por voz
@@ -863,6 +918,10 @@ with demo: # Ahora el bloque 'with' utiliza el objeto demo ya creado
863
  # Output oculto para el texto que MateAI debe vocalizar
864
  voice_output_text = gr.Textbox(elem_id="voice_output_text", visible=False)
865
 
 
 
 
 
866
  gr.Markdown("<p id='voice_status' style='text-align: center; font-weight: bold; color: #4b5563;'>Listo para hablar 🎤</p>")
867
  gr.Button("🎙️ Empezar a Hablar con MateAI 🎙️", variant="secondary", elem_id="start_voice_button").click(
868
  fn=None,
@@ -871,16 +930,35 @@ with demo: # Ahora el bloque 'with' utiliza el objeto demo ya creado
871
  js="startListening()"
872
  )
873
 
874
- # --- Nuevo Menú de Navegación Estilo Apple Watch ---
875
  gr.Markdown("<h2 style='text-align: center; color: #38bdf8; margin-top: 2em;'>Navegación MateAI</h2>")
876
- with gr.Column(elem_classes="menu-grid"):
877
- btn_home = gr.Button("<div class='icon'>🏠</div>Inicio & Perfil", elem_classes="menu-button", value="home")
878
- btn_oracle = gr.Button("<div class='icon'>✨</div>Oráculo del Día", elem_classes="menu-button", value="oracle")
879
- btn_nudges = gr.Button("<div class='icon'>💬</div>Susurros del Momento", elem_classes="menu-button", value="nudges")
880
- btn_challenges = gr.Button("<div class='icon'>🎯</div>Desafíos MateAI", elem_classes="menu-button", value="challenges")
881
- btn_history = gr.Button("<div class='icon'>📜</div>Mi Historial & Logros", elem_classes="menu-button", value="history")
882
- btn_tasks = gr.Button("<div class='icon'>📝</div>Mi Gestor de Tareas", elem_classes="menu-button", value="tasks")
883
- btn_visual_assistant = gr.Button("<div class='icon'>👁️</div>Asistente Visual (Concepto)", elem_classes="menu-button", value="visual_assistant")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
884
 
885
  # --- Contenido de cada Sección (Ahora en gr.Group o gr.Column) ---
886
  # La sección de susurros ahora es un chat
@@ -988,57 +1066,71 @@ with demo: # Ahora el bloque 'with' utiliza el objeto demo ya creado
988
  btn_completar_tarea = gr.Button("Completar Tarea", variant="secondary")
989
  output_completar_tarea = gr.Textbox(label="Estado de Completado", interactive=False)
990
 
991
- with gr.Column(elem_id="visual_assistant_section", elem_classes="section-content", visible=False):
992
  gr.Markdown(
993
  """
994
- <h2 style='text-align: center; color: #10b981;'>✨ MateAI: Tu Asistente Visual del Mañana ✨</h2>
995
  <p style='text-align: center;'>
996
- Imaginemos el futuro: MateAI como un asistente visual que te ayuda con tareas cotidianas de forma intuitiva.
997
- <br><b>Importante:</b> Como aplicación web, MateAI no puede controlar tu sistema operativo o el mouse por seguridad.
998
- Esta sección es una <b>simulación conceptual</b> de lo que MateAI podría hacer en una aplicación nativa,
999
- con un diseño inspirado en la simplicidad y la intuición de un Apple Watch.
1000
  </p>
1001
  """
1002
  )
1003
  with gr.Row():
1004
  with gr.Column():
1005
- gr.Markdown("<h3 style='color: #38bdf8;'>Acciones Rápidas (Simuladas)</h3>")
1006
- # Botones con íconos para simular acciones
1007
- btn_cerrar_pagina = gr.Button("❌ Cerrar Sección Actual", variant="secondary")
1008
- btn_abrir_nueva = gr.Button("➕ Abrir Nueva Sección", variant="secondary")
1009
- btn_scroll_arriba = gr.Button("⬆️ Subir Página", variant="secondary")
1010
- btn_scroll_abajo = gr.Button("⬇️ Bajar Página", variant="secondary")
1011
-
1012
- simulated_action_output = gr.Textbox(label="Resultado de la Acción (Simulada)", interactive=False, lines=2)
1013
-
1014
- gr.Markdown(
1015
  """
1016
- <p style='font-size: 0.9em; color: #6b7280; margin-top: 1em;'>
1017
- <i>En una aplicación nativa, estas acciones se integrarían directamente con tu sistema.</i>
1018
- </p>
 
 
 
 
 
 
 
 
1019
  """
1020
  )
1021
- with gr.Column():
1022
- gr.Markdown("<h3 style='color: #38bdf8;'>Interacción Visual (Conceptual)</h3>")
1023
- gr.Image(
1024
- value="https://placehold.co/400x300/e0f2fe/0369a1?text=Interfaz+Visual+Conceptual",
1025
- label="Visualización de Interacción (Conceptual)",
1026
- interactive=False,
1027
- show_label=True
1028
- )
1029
  gr.Markdown(
1030
  """
1031
  <p style='font-size: 0.9em; color: #6b7280; margin-top: 1em;'>
1032
- Imagina MateAI detectando lo que ves en pantalla y ofreciendo opciones contextuales con íconos,
1033
- como un "toque" para seleccionar elementos o un "deslizar" para navegar.
1034
- Ideal para asistencia motriz, con feedback háptico y auditivo sutil.
1035
  </p>
1036
  """
1037
  )
1038
 
1039
  # Funciones simuladas para el asistente visual
1040
- def simulate_action(action_name):
1041
- return f"Simulando: '{action_name}'. En una aplicación nativa, MateAI realizaría esta acción por ti."
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1042
 
1043
  # --- Funciones de Interacción para Gradio ---
1044
 
@@ -1051,7 +1143,7 @@ with demo: # Ahora el bloque 'with' utiliza el objeto demo ya creado
1051
  "challenges_section": gr.Column.update(visible=view_name == "challenges"),
1052
  "history_section": gr.Column.update(visible=view_name == "history"),
1053
  "tasks_section": gr.Column.update(visible=view_name == "tasks"),
1054
- "visual_assistant_section": gr.Column.update(visible=view_name == "visual_assistant"),
1055
  current_view_state: view_name # Actualiza el estado de la vista
1056
  }
1057
 
@@ -1122,14 +1214,6 @@ with demo: # Ahora el bloque 'with' utiliza el objeto demo ya creado
1122
  return f"Preferencias actualizadas para {user_obj.name}.", user_obj
1123
  return "Error al actualizar preferencias.", user_obj
1124
 
1125
- async def _generate_nudge_gradio(user_obj, location, activity, sentiment):
1126
- if not user_obj:
1127
- return "Por favor, crea o carga un usuario primero para recibir susurros personalizados.", "", "", "", "", ""
1128
-
1129
- nudge_text, current_points, current_insignia, next_insignia_goal, historial = await nudge_generator.generate_nudge(user_obj.user_id, location, activity, sentiment)
1130
-
1131
- return nudge_text, str(current_points), current_insignia, next_insignia_goal, historial, nudge_text # Retorna el texto para vocalizar
1132
-
1133
  async def _get_oracle_revelation_gradio(user_obj):
1134
  if not user_obj:
1135
  return "Por favor, crea o carga un usuario primero para consultar al Oráculo.", ""
@@ -1143,34 +1227,18 @@ with demo: # Ahora el bloque 'with' utiliza el objeto demo ya creado
1143
  return challenge_text, challenge_text # Retorna el texto para vocalizar
1144
 
1145
  async def _add_task_gradio(user_obj, task_name):
1146
- if not user_id: # Check if user_id is None or empty
1147
- return "Error: Por favor, crea o carga un usuario primero.", "" # Return default values for outputs
1148
- user = await self.user_manager.get_user(user_id)
1149
- if not user:
1150
- return "Error: Usuario no logueado. Por favor, crea o carga un usuario.", ""
1151
-
1152
- new_task = {"task": task_name, "added_at": datetime.now().strftime('%Y-%m-%d %H:%M:%S.%f'), "last_nudged": None}
1153
- user.tasks.append(new_task)
1154
- await self.user_manager.update_user_data(user)
1155
- 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.tasks])
1156
- return f"¡Tarea '{task_name}' agregada a tu lista, {user.name}!", tareas_str
1157
 
1158
  async def _complete_task_gradio(user_obj, task_name):
1159
- if not user_id: # Check if user_id is None or empty
1160
- return "Error: Por favor, crea o carga un usuario primero.", "" # Return default values for outputs
1161
- user = await self.user_manager.get_user(user_id)
1162
- if not user:
1163
- return "Error: Usuario no logueado. Por favor, crea o carga un usuario.", ""
1164
-
1165
- initial_task_count = len(user.tasks)
1166
- user.tasks = [task for task in user.tasks if task['task'].lower() != task_name.lower()]
1167
-
1168
- if len(user.tasks) < initial_task_count:
1169
- await self.user_manager.update_user_data(user)
1170
- 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.tasks])
1171
- return f"¡Tarea '{task_name}' marcada como completada, {user.name}! ¡Bien ahí!", tareas_str
1172
- 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.tasks])
1173
- return f"No encontré la tarea '{task_name}' en tu lista, {user.name}.", tareas_str
1174
 
1175
  def _download_diary_gradio(diary_text):
1176
  return gr.File(value=diary_text.encode('utf-8'), filename="diario_mateai.txt", type="bytes")
@@ -1215,6 +1283,46 @@ with demo: # Ahora el bloque 'with' utiliza el objeto demo ya creado
1215
  else:
1216
  chat_history.append((user_message, "¡Decime qué tarea completaste, así la saco de la lista! Por ejemplo: 'completar tarea ir al súper'."))
1217
  return chat_history, "", conversation_context_state, "¡Decime qué tarea completaste, así la saco de la lista! Por ejemplo: 'completar tarea ir al súper'."
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1218
 
1219
  # Lógica para pedir un susurro contextual
1220
  current_step = conversation_context_state["step"]
@@ -1242,7 +1350,7 @@ with demo: # Ahora el bloque 'with' utiliza el objeto demo ya creado
1242
  chat_history.append((user_message, response))
1243
  # Reiniciar contexto después de generar el nudge
1244
  conversation_context_state = {"step": "initial", "pending_nudge_request": False, "location": None, "activity": None, "sentiment": None}
1245
- return chat_history, "", conversation_context_state, response
1246
 
1247
  elif current_step == "ask_location":
1248
  if any(k in user_message_lower for k in ["casa", "hogar"]):
@@ -1253,7 +1361,7 @@ with demo: # Ahora el bloque 'with' utiliza el objeto demo ya creado
1253
  location = "Aire Libre/Calle"
1254
  else:
1255
  chat_history.append((user_message, "No te entendí bien, che. ¿Estás en casa, en la oficina o en la calle?"))
1256
- return chat_history, "", conversation_context_state, ""
1257
 
1258
  conversation_context_state["location"] = location
1259
  if not activity:
@@ -1267,7 +1375,7 @@ with demo: # Ahora el bloque 'with' utiliza el objeto demo ya creado
1267
  response = nudge_text
1268
  chat_history.append((user_message, response))
1269
  conversation_context_state = {"step": "initial", "pending_nudge_request": False, "location": None, "activity": None, "sentiment": None}
1270
- return chat_history, "", conversation_context_state, response
1271
 
1272
  elif current_step == "ask_activity":
1273
  if any(k in user_message_lower for k in ["relajado", "tranqui", "descansando"]):
@@ -1284,7 +1392,7 @@ with demo: # Ahora el bloque 'with' utiliza el objeto demo ya creado
1284
  activity = "Viendo un partido"
1285
  else:
1286
  chat_history.append((user_message, "No te pesqué esa, che. ¿Estás laburando, haciendo ejercicio, relajado, cocinando, o viendo un partido?"))
1287
- return chat_history, "", conversation_context_state, ""
1288
 
1289
  conversation_context_state["activity"] = activity
1290
  if not sentiment:
@@ -1295,7 +1403,7 @@ with demo: # Ahora el bloque 'with' utiliza el objeto demo ya creado
1295
  response = nudge_text
1296
  chat_history.append((user_message, response))
1297
  conversation_context_state = {"step": "initial", "pending_nudge_request": False, "location": None, "activity": None, "sentiment": None}
1298
- return chat_history, "", conversation_context_state, response
1299
 
1300
  elif current_step == "ask_sentiment":
1301
  if any(k in user_message_lower for k in ["bien", "joya", "diez puntos", "contento"]):
@@ -1308,7 +1416,7 @@ with demo: # Ahora el bloque 'with' utiliza el objeto demo ya creado
1308
  sentiment = "Motivado"
1309
  else:
1310
  chat_history.append((user_message, "No te capto el sentimiento, che. ¿Estás bien, cansado, bajoneado o motivado?"))
1311
- return chat_history, "", conversation_context_state, ""
1312
 
1313
  conversation_context_state["sentiment"] = sentiment
1314
  # Ahora que tenemos todo, generar el nudge
@@ -1317,15 +1425,12 @@ with demo: # Ahora el bloque 'with' utiliza el objeto demo ya creado
1317
  chat_history.append((user_message, response))
1318
  # Reiniciar contexto después de generar el nudge
1319
  conversation_context_state = {"step": "initial", "pending_nudge_request": False, "location": None, "activity": None, "sentiment": None}
1320
- return chat_history, "", conversation_context_state, response
1321
 
1322
  else: # Default response if no specific command or context step
1323
  response = "¡No te entendí bien, che! Soy MateAI, tu compañero. Si querés un susurro, decime 'quiero un susurro' y te voy a preguntar unas cositas. Si no, decime qué andás necesitando."
1324
  chat_history.append((user_message, response))
1325
- return chat_history, "", conversation_context_state, response
1326
-
1327
- chat_history.append((user_message, response))
1328
- return chat_history, "", conversation_context_state, response
1329
 
1330
  # --- Conexión de Eventos ---
1331
  btn_crear_usuario.click(
@@ -1346,14 +1451,6 @@ with demo: # Ahora el bloque 'with' utiliza el objeto demo ya creado
1346
  outputs=[output_actualizar_preferencias, current_user_state]
1347
  )
1348
 
1349
- # El botón de generar susurro en la sección de susurros ya no se usa directamente para generar,
1350
- # sino que se activa a través de la conversación.
1351
- # btn_generar_susurro.click(
1352
- # fn=_generate_nudge_gradio,
1353
- # inputs=[current_user_state, ubicacion_input, actividad_input, estado_animo_input],
1354
- # outputs=[susurro_output, usuario_actual_puntos, usuario_actual_insignia, usuario_proxima_insignia, historial_susurros_output, voice_output_text]
1355
- # )
1356
-
1357
  btn_oraculo.click(
1358
  fn=_get_oracle_revelation_gradio,
1359
  inputs=[current_user_state],
@@ -1384,47 +1481,25 @@ with demo: # Ahora el bloque 'with' utiliza el objeto demo ya creado
1384
  outputs=gr.File(label="Descargar tu Diario")
1385
  )
1386
 
1387
- # Conexión de funciones simuladas para el Asistente Visual
1388
- btn_cerrar_pagina.click(
1389
- fn=lambda: simulate_action("Cerrar Sección Actual"),
1390
- inputs=[],
1391
- outputs=[simulated_action_output, voice_output_text] # También vocaliza la simulación
1392
- )
1393
- btn_abrir_nueva.click(
1394
- fn=lambda: simulate_action("Abrir Nueva Sección"),
1395
- inputs=[],
1396
- outputs=[simulated_action_output, voice_output_text]
1397
- )
1398
- btn_scroll_arriba.click(
1399
- fn=lambda: simulate_action("Subir Página"),
1400
- inputs=[],
1401
- outputs=[simulated_action_output, voice_output_text]
1402
- )
1403
- btn_scroll_abajo.click(
1404
- fn=lambda: simulate_action("Bajar Página"),
1405
- inputs=[],
1406
- outputs=[simulated_action_output, voice_output_text]
1407
- )
1408
-
1409
  # --- Manejo de la entrada de voz y texto conversacional ---
1410
  # El input de texto (msg_input) y el botón de enviar (btn_send_msg)
1411
  # ahora se conectan a la función _process_conversational_input
1412
  msg_input.submit(
1413
  fn=_process_conversational_input,
1414
  inputs=[msg_input, chat_history, current_user_state, conversation_context_state],
1415
- outputs=[chatbot, msg_input, conversation_context_state, voice_output_text]
1416
  )
1417
  btn_send_msg.click(
1418
  fn=_process_conversational_input,
1419
  inputs=[msg_input, chat_history, current_user_state, conversation_context_state],
1420
- outputs=[chatbot, msg_input, conversation_context_state, voice_output_text]
1421
  )
1422
 
1423
  # El botón oculto para la entrada de voz también se conecta a la función conversacional
1424
  hidden_voice_submit_button.click(
1425
  fn=_process_conversational_input,
1426
  inputs=[voice_input_textbox, chat_history, current_user_state, conversation_context_state],
1427
- outputs=[chatbot, voice_input_textbox, conversation_context_state, voice_output_text]
1428
  )
1429
 
1430
  # Cuando el backend genera texto para voz, lo envía a voice_output_text,
@@ -1436,46 +1511,16 @@ with demo: # Ahora el bloque 'with' utiliza el objeto demo ya creado
1436
  js="text => window.speakText(text)"
1437
  )
1438
 
1439
- # --- Conexión de los botones del menú a la función change_view ---
1440
  all_sections = [
1441
  "home_section", "oracle_section", "nudges_section",
1442
  "challenges_section", "history_section", "tasks_section",
1443
- "visual_assistant_section"
1444
  ]
1445
 
1446
- btn_home.click(
1447
- fn=lambda: change_view("home"),
1448
- inputs=[],
1449
- outputs=[gr.Column(elem_id=s) for s in all_sections] + [current_view_state]
1450
- )
1451
- btn_oracle.click(
1452
- fn=lambda: change_view("oracle"),
1453
- inputs=[],
1454
- outputs=[gr.Column(elem_id=s) for s in all_sections] + [current_view_state]
1455
- )
1456
- btn_nudges.click(
1457
- fn=lambda: change_view("nudges"),
1458
- inputs=[],
1459
- outputs=[gr.Column(elem_id=s) for s in all_sections] + [current_view_state]
1460
- )
1461
- btn_challenges.click(
1462
- fn=lambda: change_view("challenges"),
1463
- inputs=[],
1464
- outputs=[gr.Column(elem_id=s) for s in all_sections] + [current_view_state]
1465
- )
1466
- btn_history.click(
1467
- fn=lambda: change_view("history"),
1468
- inputs=[],
1469
- outputs=[gr.Column(elem_id=s) for s in all_sections] + [current_view_state]
1470
- )
1471
- btn_tasks.click(
1472
- fn=lambda: change_view("tasks"),
1473
- inputs=[],
1474
- outputs=[gr.Column(elem_id=s) for s in all_sections] + [current_view_state]
1475
- )
1476
- btn_visual_assistant.click(
1477
- fn=lambda: change_view("visual_assistant"),
1478
- inputs=[],
1479
  outputs=[gr.Column(elem_id=s) for s in all_sections] + [current_view_state]
1480
  )
1481
 
 
10
  import firebase_admin
11
  from firebase_admin import credentials, firestore
12
 
13
+ # --- MateAI: El Super Agente Argentino - Rediseño Total ---
14
+ # Un compañero de IA diseñado para Argentina, con personalidad y robustez.
15
+ # Interacción principalmente vocal y conversacional, con funciones de agente simuladas.
 
 
 
 
16
 
17
  # --- Módulo 1: Configuración Global y Constantes ---
18
  class Config:
19
+ APP_NAME = "MateAI: El Super Agente Argentino"
20
  DEFAULT_USER_PREFS = {"tipo_susurro": "ambos", "frecuencia": "normal", "modo_che_tranqui": False}
21
  ECO_PUNTOS_POR_SUSURRO = 1
22
  MAX_HISTORIAL_SUSURROS = 50 # Limita el historial de susurros guardado por usuario
 
24
  TASK_NUDGE_COOLDOWN_HOURS = 4 # Cooldown para recordar la misma tarea
25
 
26
  # --- Firebase Configuration ---
27
+ # La configuración de Firebase se cargará desde la variable de entorno __firebase_config
28
+ # o se usará la configuración proporcionada por el usuario si no está definida.
29
+ # En un entorno de producción real, se recomienda usar variables de entorno seguras.
30
+ FIREBASE_COLLECTION_USERS = "artifacts/mateai-super-agente-argentino/users" # Colección para datos de usuario
 
 
 
 
 
31
 
32
  # --- Firebase Initialization ---
33
  # Intenta inicializar Firebase Admin SDK.
34
+ # Se usa la configuración proporcionada por el usuario o la variable de entorno.
35
  try:
36
+ if not firebase_admin._apps: # Verifica si la app ya está inicializada
37
+ firebase_config_str = os.getenv('__firebase_config', None)
38
+ if firebase_config_str:
39
+ firebase_config = json.loads(firebase_config_str)
40
+ cred = credentials.Certificate(firebase_config) # Asume que es un JSON de credenciales de servicio
 
 
 
41
  firebase_admin.initialize_app(cred)
42
  print("Firebase Admin SDK inicializado correctamente desde variable de entorno.")
43
+ else:
44
+ # Usar la configuración proporcionada directamente por el usuario en el prompt
45
+ firebase_config_from_user = {
46
+ "apiKey": "AIzaSyAcIuq4oECWML08y6oYIj87xzFFBl6M9QA",
47
+ "authDomain": "mateai-815ca.firebaseapp.com",
48
+ "projectId": "mateai-815ca",
49
+ "storageBucket": "mateai-815ca.firebasestorage.app",
50
+ "messagingSenderId": "686450177402",
51
+ "appId": "1:686450177402:web:fd6a1ad84b42875f5452c3",
52
+ "measurementId": "G-L6C7RKKXFH"
53
+ }
54
+ # Para initialize_app con esta configuración, necesitas el SDK de cliente, no Admin SDK.
55
+ # Sin embargo, el resto del código usa Admin SDK (firestore.client()).
56
+ # Esto es una inconsistencia. Para Gradio/Python, lo correcto es usar Admin SDK con credenciales de servicio.
57
+ # Si el usuario quiere usar la configuración web, tendría que ser en el frontend JS.
58
+ # Para simplificar y mantener la persistencia con Firestore en Python, asumiré que
59
+ # la configuración anterior era para el Admin SDK o que el usuario quiere usarlo así.
60
+ # Si la variable de entorno no está, y no se puede inicializar con credenciales de servicio,
61
+ # la persistencia de Firestore no funcionará.
62
+ print("ADVERTENCIA: GOOGLE_APPLICATION_CREDENTIALS_JSON no está configurada y no se pudo inicializar Firebase Admin SDK.")
63
+ print("La persistencia de datos de usuario en Firestore NO funcionará.")
64
+ # No inicializamos firebase_admin si no tenemos credenciales de servicio válidas.
65
+ # Esto significa que las operaciones de Firestore fallarán silenciosamente o con errores.
66
+ # Para una demo, se puede dejar así, pero para prod, es crítico.
67
+ except ValueError:
68
+ pass # Ya inicializado
69
 
70
+ db = firestore.client() if firebase_admin._apps else None # Solo si Firebase se inicializó con éxito
71
 
72
  # --- Módulo 2: Gestión de Usuarios (UserManager) ---
73
  # Gestiona la creación, carga y actualización de usuarios con persistencia en Firestore.
74
  class UserManager:
75
  @classmethod
76
  async def create_user(cls, name, initial_prefs=None):
77
+ if not db: return None, "Error: La base de datos no está disponible."
78
  user_id = f"user_{int(time.time())}_{random.randint(1000, 9999)}" # ID único basado en tiempo y random
79
  prefs = initial_prefs if initial_prefs else Config.DEFAULT_USER_PREFS.copy()
80
  new_user = User(user_id, name, prefs)
81
 
82
  user_doc_ref = db.collection(Config.FIREBASE_COLLECTION_USERS).document(user_id)
83
  try:
 
84
  user_doc_ref.set(new_user.to_dict())
85
  return new_user, f"¡Bienvenido, {name}! Tu ID de usuario es: {user_id}. ¡MateAI está listo para cebarte la vida!"
86
  except Exception as e:
 
88
 
89
  @classmethod
90
  async def login_user(cls, user_id):
91
+ if not db: return None, "Error: La base de datos no está disponible."
92
  user_doc_ref = db.collection(Config.FIREBASE_COLLECTION_USERS).document(user_id)
93
  try:
 
94
  doc = user_doc_ref.get()
95
  if doc.exists:
96
  user_data = doc.to_dict()
 
111
 
112
  @classmethod
113
  async def get_user(cls, user_id):
114
+ if not db: return None
115
  user_doc_ref = db.collection(Config.FIREBASE_COLLECTION_USERS).document(user_id)
116
  try:
 
117
  doc = user_doc_ref.get()
118
  if doc.exists:
119
  user_data = doc.to_dict()
 
134
 
135
  @classmethod
136
  async def update_user_data(cls, user_obj):
137
+ if not db: return False
138
  user_doc_ref = db.collection(Config.FIREBASE_COLLECTION_USERS).document(user_obj.user_id)
139
  try:
 
140
  user_doc_ref.update(user_obj.to_dict())
141
  return True
142
  except Exception as e:
 
508
  return "Error: Usuario no logueado. Por favor, crea o carga un usuario."
509
 
510
  today = datetime.now().date()
 
511
  if user.last_oracle_date and user.last_oracle_date == today:
512
  return "El Oráculo ya te ha hablado hoy. Volvé mañana para una nueva revelación."
513
 
 
531
  return formatted_challenge
532
 
533
  async def add_task(self, user_id, task_name):
534
+ if not user_id:
535
+ return "Error: Por favor, crea o carga un usuario primero.", ""
536
  user = await self.user_manager.get_user(user_id)
537
  if not user:
538
  return "Error: Usuario no logueado. Por favor, crea o carga un usuario.", ""
 
544
  return f"¡Tarea '{task_name}' agregada a tu lista, {user.name}!", tareas_str
545
 
546
  async def complete_task(self, user_id, task_name):
547
+ if not user_id:
548
+ return "Error: Por favor, crea o carga un usuario primero.", ""
549
  user = await self.user_manager.get_user(user_id)
550
  if not user:
551
  return "Error: Usuario no logueado. Por favor, crea o carga un usuario.", ""
 
610
  gr.Markdown(
611
  """
612
  <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);">
613
+ 🧉 MateAI: El Super Agente Argentino 🧉
614
  </h1>
615
  <p style="text-align: center; color: #4b5563; font-size: 1.3em; margin-bottom: 2em; line-height: 1.5;">
616
  Soy MateAI, tu compañero argentino. Estoy acá para cebarte la vida con sabiduría contextual y sin costo.
 
697
  border-radius: 20px;
698
  box-shadow: 0 8px 20px rgba(0,0,0,0.1);
699
  }
700
+ .menu-button-custom { /* Clase para los botones personalizados con iconos */
701
  display: flex;
702
  flex-direction: column;
703
  align-items: center;
 
714
  text-align: center;
715
  padding: 10px;
716
  }
717
+ .menu-button-custom:hover {
718
  background-color: #e0f2fe; /* sky-100 */
719
  border-color: #38bdf8; /* sky-400 */
720
  transform: translateY(-3px);
721
  box-shadow: 0 4px 10px rgba(0,0,0,0.1);
722
  }
723
+ .menu-button-custom .icon {
724
  font-size: 2.5em; /* Tamaño del icono */
725
  margin-bottom: 5px;
726
  }
 
731
  box-shadow: 0 8px 20px rgba(0,0,0,0.1);
732
  margin-top: 20px;
733
  }
734
+ /* Estilos para el navegador simulado */
735
+ .browser-frame {
736
+ border: 1px solid #ccc;
737
+ border-radius: 8px;
738
+ overflow: hidden;
739
+ box-shadow: 0 4px 8px rgba(0,0,0,0.1);
740
+ }
741
+ .browser-header {
742
+ background-color: #f1f1f1;
743
+ padding: 8px 12px;
744
+ border-bottom: 1px solid #e0e0e0;
745
+ display: flex;
746
+ align-items: center;
747
+ gap: 8px;
748
+ }
749
+ .browser-address-bar {
750
+ flex-grow: 1;
751
+ background-color: #fff;
752
+ border: 1px solid #ddd;
753
+ border-radius: 4px;
754
+ padding: 6px 10px;
755
+ font-size: 0.9em;
756
+ color: #333;
757
+ }
758
+ .browser-content {
759
+ width: 100%;
760
+ height: 400px; /* Altura fija para la simulación */
761
+ background-color: #f9f9f9;
762
+ display: flex;
763
+ align-items: center;
764
+ justify-content: center;
765
+ font-size: 1.2em;
766
+ color: #666;
767
+ text-align: center;
768
+ }
769
  </style>
770
  <div id="matecito_avatar" class="matecito-avatar">🧉</div>
771
  <script>
 
828
  voiceStatus.textContent = 'No se detectó voz. Intenta hablar más claro o más fuerte. 🎤';
829
  voiceStatus.style.color = '#fbbf24'; // Amber
830
  } else if (event.error === 'aborted') {
831
+ voiceStatus.textContent = 'Reconocimiento de voz cancelado. 🎤';
832
  voiceStatus.style.color = '#4b5563';
833
  }
834
  };
 
844
  const utterance = new SpeechSynthesisUtterance(text);
845
  utterance.lang = 'es-AR'; // Español de Argentina
846
 
847
+ // Intenta encontrar una voz en español de Argentina y varón
848
  const voices = window.speechSynthesis.getVoices();
849
+ const preferredVoice = voices.find(voice =>
850
+ voice.lang === 'es-AR' &&
851
+ (voice.name.includes('Argentina') || voice.name.includes('Diego') || voice.name.includes('male'))
852
+ ) || voices.find(voice =>
853
+ voice.lang.startsWith('es') && voice.name.includes('male')
854
+ ) || voices.find(voice => voice.lang === 'es-AR') ||
855
+ voices.find(voice => voice.lang.startsWith('es'));
856
+
857
  if (preferredVoice) {
858
  utterance.voice = preferredVoice;
859
  } else {
860
+ console.warn("No se encontró una voz en español de Argentina o genérica masculina. Usando la voz por defecto.");
861
  }
862
 
863
  utterance.onstart = function() {
 
886
  utterance.onerror = function(event) {
887
  speaking = false;
888
  console.error('Speech synthesis error:', event.error);
889
+ voiceStatus.textContent = 'Error al hablar ';
890
  voiceStatus.style.color = '#ef4444'; // Red
891
  matecitoAvatar.classList.remove('matecito-talking');
892
  matecitoAvatar.style.animation = 'fade-out 0.3s forwards';
 
898
 
899
  // Exponer la función speakText globalmente para que Gradio pueda llamarla
900
  window.speakText = speakText;
901
+
902
+ // Función para cambiar la vista de Gradio (llamada desde los botones HTML personalizados)
903
+ function changeGradioView(viewName) {
904
+ const hiddenButton = document.getElementById('hidden_view_changer_button');
905
+ const viewInput = document.getElementById('hidden_view_input');
906
+ if (hiddenButton && viewInput) {
907
+ viewInput.value = viewName;
908
+ hiddenButton.click();
909
+ }
910
+ }
911
+ window.changeGradioView = changeGradioView; // Exponer globalmente
912
  </script>
913
  """)
914
  # Input oculto para el texto reconocido por voz
 
918
  # Output oculto para el texto que MateAI debe vocalizar
919
  voice_output_text = gr.Textbox(elem_id="voice_output_text", visible=False)
920
 
921
+ # Componentes ocultos para el cambio de vista desde JS
922
+ hidden_view_input = gr.Textbox(elem_id="hidden_view_input", visible=False)
923
+ hidden_view_changer_button = gr.Button("Change View", elem_id="hidden_view_changer_button", visible=False)
924
+
925
  gr.Markdown("<p id='voice_status' style='text-align: center; font-weight: bold; color: #4b5563;'>Listo para hablar 🎤</p>")
926
  gr.Button("🎙️ Empezar a Hablar con MateAI 🎙️", variant="secondary", elem_id="start_voice_button").click(
927
  fn=None,
 
930
  js="startListening()"
931
  )
932
 
933
+ # --- Nuevo Menú de Navegación con HTML personalizado ---
934
  gr.Markdown("<h2 style='text-align: center; color: #38bdf8; margin-top: 2em;'>Navegación MateAI</h2>")
935
+ gr.HTML(
936
+ """
937
+ <div class="menu-grid">
938
+ <div class="menu-button-custom" onclick="changeGradioView('home')">
939
+ <div class="icon">🏠</div>Inicio & Perfil
940
+ </div>
941
+ <div class="menu-button-custom" onclick="changeGradioView('nudges')">
942
+ <div class="icon">💬</div>Conversar con MateAI
943
+ </div>
944
+ <div class="menu-button-custom" onclick="changeGradioView('oracle')">
945
+ <div class="icon">✨</div>Oráculo del Día
946
+ </div>
947
+ <div class="menu-button-custom" onclick="changeGradioView('challenges')">
948
+ <div class="icon">🎯</div>Desafíos MateAI
949
+ </div>
950
+ <div class="menu-button-custom" onclick="changeGradioView('tasks')">
951
+ <div class="icon">📝</div>Mi Gestor de Tareas
952
+ </div>
953
+ <div class="menu-button-custom" onclick="changeGradioView('history')">
954
+ <div class="icon">📜</div>Mi Historial & Logros
955
+ </div>
956
+ <div class="menu-button-custom" onclick="changeGradioView('browser_agent')">
957
+ <div class="icon">🌐</div>Asistente Web (Concepto)
958
+ </div>
959
+ </div>
960
+ """
961
+ )
962
 
963
  # --- Contenido de cada Sección (Ahora en gr.Group o gr.Column) ---
964
  # La sección de susurros ahora es un chat
 
1066
  btn_completar_tarea = gr.Button("Completar Tarea", variant="secondary")
1067
  output_completar_tarea = gr.Textbox(label="Estado de Completado", interactive=False)
1068
 
1069
+ with gr.Column(elem_id="browser_agent_section", elem_classes="section-content", visible=False):
1070
  gr.Markdown(
1071
  """
1072
+ <h2 style='text-align: center; color: #10b981;'>🌐 MateAI: Tu Asistente Web (Concepto) 🌐</h2>
1073
  <p style='text-align: center;'>
1074
+ Imaginemos el futuro: MateAI como un agente que te ayuda a navegar y operar en la web de forma conversacional.
1075
+ <br><b>Importante:</b> Como aplicación web, MateAI no puede controlar tu sistema operativo o el mouse/teclado fuera de esta ventana por seguridad.
1076
+ Esta sección es una <b>simulación conceptual</b> de lo que MateAI podría hacer en una aplicación nativa o con permisos especiales,
1077
+ operando sobre un navegador *dentro de esta misma ventana*.
1078
  </p>
1079
  """
1080
  )
1081
  with gr.Row():
1082
  with gr.Column():
1083
+ gr.Markdown("<h3 style='color: #38bdf8;'>Navegador Simulado</h3>")
1084
+ simulated_browser_url = gr.Textbox(label="URL Actual (Simulada)", value="https://www.google.com.ar", interactive=False)
1085
+ simulated_browser_content = gr.HTML(
 
 
 
 
 
 
 
1086
  """
1087
+ <div class="browser-frame">
1088
+ <div class="browser-header">
1089
+ <span style="color: #666;">&#9679; &#9679; &#9679;</span>
1090
+ <div class="browser-address-bar" id="simulated_browser_address">https://www.google.com.ar</div>
1091
+ </div>
1092
+ <div class="browser-content" id="simulated_browser_content">
1093
+ <p>¡Bienvenido al navegador simulado de MateAI!</p>
1094
+ <p>Decile a MateAI qué querés hacer, por ejemplo: "Andá a Google", "Buscá el clima en Buenos Aires".</p>
1095
+ <p>Recordá que esto es una simulación. MateAI te va a contar qué haría.</p>
1096
+ </div>
1097
+ </div>
1098
  """
1099
  )
1100
+
1101
+ gr.Markdown("<h3 style='color: #38bdf8; margin-top: 1.5em;'>Acciones Sugeridas por MateAI (Simuladas)</h3>")
1102
+ simulated_action_output = gr.Textbox(label="Lo que MateAI haría...", interactive=False, lines=2)
1103
+
 
 
 
 
1104
  gr.Markdown(
1105
  """
1106
  <p style='font-size: 0.9em; color: #6b7280; margin-top: 1em;'>
1107
+ <i>En una aplicación nativa, estas acciones se integrarían directamente con tu sistema.</i>
 
 
1108
  </p>
1109
  """
1110
  )
1111
 
1112
  # Funciones simuladas para el asistente visual
1113
+ def simulate_browser_action(action_description, new_url=None, new_content=None):
1114
+ if new_url:
1115
+ js_code = f"""
1116
+ document.getElementById('simulated_browser_address').innerText = '{new_url}';
1117
+ document.getElementById('simulated_browser_content').innerHTML = '<p>Cargando: {new_url}...</p>';
1118
+ """
1119
+ if "google.com" in new_url:
1120
+ js_code += """
1121
+ document.getElementById('simulated_browser_content').innerHTML = '<p>¡Estás en Google! ¿Qué querés buscar?</p><img src="https://placehold.co/200x100/ffffff/000000?text=Google" alt="Google Logo">';
1122
+ """
1123
+ elif "wikipedia.org" in new_url:
1124
+ js_code += """
1125
+ document.getElementById('simulated_browser_content').innerHTML = '<p>¡Estás en Wikipedia! Buscando información...</p><img src="https://placehold.co/200x100/ffffff/000000?text=Wikipedia" alt="Wikipedia Logo">';
1126
+ """
1127
+ else:
1128
+ js_code += f"""
1129
+ document.getElementById('simulated_browser_content').innerHTML = '<p>Contenido simulado de: {new_url}</p>';
1130
+ """
1131
+ return action_description, new_url, gr.HTML.update(value=js_code)
1132
+
1133
+ return action_description, gr.skip(), gr.skip() # No cambia URL ni contenido si no se especifica
1134
 
1135
  # --- Funciones de Interacción para Gradio ---
1136
 
 
1143
  "challenges_section": gr.Column.update(visible=view_name == "challenges"),
1144
  "history_section": gr.Column.update(visible=view_name == "history"),
1145
  "tasks_section": gr.Column.update(visible=view_name == "tasks"),
1146
+ "browser_agent_section": gr.Column.update(visible=view_name == "browser_agent"), # Actualizado
1147
  current_view_state: view_name # Actualiza el estado de la vista
1148
  }
1149
 
 
1214
  return f"Preferencias actualizadas para {user_obj.name}.", user_obj
1215
  return "Error al actualizar preferencias.", user_obj
1216
 
 
 
 
 
 
 
 
 
1217
  async def _get_oracle_revelation_gradio(user_obj):
1218
  if not user_obj:
1219
  return "Por favor, crea o carga un usuario primero para consultar al Oráculo.", ""
 
1227
  return challenge_text, challenge_text # Retorna el texto para vocalizar
1228
 
1229
  async def _add_task_gradio(user_obj, task_name):
1230
+ user_id = user_obj.user_id if user_obj else None
1231
+ if not user_id:
1232
+ return "Error: Por favor, crea o carga un usuario primero.", ""
1233
+ msg, tareas_str = await nudge_generator.add_task(user_id, task_name)
1234
+ return msg, tareas_str
 
 
 
 
 
 
1235
 
1236
  async def _complete_task_gradio(user_obj, task_name):
1237
+ user_id = user_obj.user_id if user_obj else None
1238
+ if not user_id:
1239
+ return "Error: Por favor, crea o carga un usuario primero.", ""
1240
+ msg, tareas_str = await nudge_generator.complete_task(user_id, task_name)
1241
+ return msg, tareas_str
 
 
 
 
 
 
 
 
 
 
1242
 
1243
  def _download_diary_gradio(diary_text):
1244
  return gr.File(value=diary_text.encode('utf-8'), filename="diario_mateai.txt", type="bytes")
 
1283
  else:
1284
  chat_history.append((user_message, "¡Decime qué tarea completaste, así la saco de la lista! Por ejemplo: 'completar tarea ir al súper'."))
1285
  return chat_history, "", conversation_context_state, "¡Decime qué tarea completaste, así la saco de la lista! Por ejemplo: 'completar tarea ir al súper'."
1286
+
1287
+ # --- Agente de Navegador Conversacional (Simulado) ---
1288
+ if "abrir" in user_message_lower or "ir a" in user_message_lower or "navegar" in user_message_lower:
1289
+ if "google" in user_message_lower:
1290
+ action_desc = "¡Dale, abriendo Google en el navegador simulado!"
1291
+ new_url = "https://www.google.com.ar"
1292
+ chat_history.append((user_message, action_desc))
1293
+ return chat_history, "", conversation_context_state, action_desc, new_url, gr.HTML.update(value=simulate_browser_action(action_desc, new_url)[2].value)
1294
+ elif "wikipedia" in user_message_lower:
1295
+ action_desc = "¡Perfecto! Abriendo Wikipedia en el navegador simulado."
1296
+ new_url = "https://es.wikipedia.org"
1297
+ chat_history.append((user_message, action_desc))
1298
+ return chat_history, "", conversation_context_state, action_desc, new_url, gr.HTML.update(value=simulate_browser_action(action_desc, new_url)[2].value)
1299
+ elif "pagina de inicio" in user_message_lower or "inicio" in user_message_lower:
1300
+ action_desc = "Volviendo a la página de inicio del navegador simulado."
1301
+ new_url = "https://www.google.com.ar" # O la URL por defecto que quieras
1302
+ chat_history.append((user_message, action_desc))
1303
+ return chat_history, "", conversation_context_state, action_desc, new_url, gr.HTML.update(value=simulate_browser_action(action_desc, new_url)[2].value)
1304
+ else:
1305
+ response = "¡Che, a qué página querés ir? Decime, por ejemplo: 'abrir Google' o 'ir a Wikipedia'."
1306
+ chat_history.append((user_message, response))
1307
+ return chat_history, "", conversation_context_state, response, gr.skip(), gr.skip()
1308
+
1309
+ if "buscar" in user_message_lower and "en google" in user_message_lower:
1310
+ query = user_message_lower.replace("buscar", "").replace("en google", "").strip()
1311
+ if query:
1312
+ action_desc = f"¡Buscando '{query}' en Google!"
1313
+ new_url = f"https://www.google.com.ar/search?q={query.replace(' ', '+')}"
1314
+ chat_history.append((user_message, action_desc))
1315
+ return chat_history, "", conversation_context_state, action_desc, new_url, gr.HTML.update(value=simulate_browser_action(action_desc, new_url)[2].value)
1316
+ else:
1317
+ response = "¡Decime qué querés buscar, che! Por ejemplo: 'buscar el clima en Google'."
1318
+ chat_history.append((user_message, response))
1319
+ return chat_history, "", conversation_context_state, response, gr.skip(), gr.skip()
1320
+
1321
+ if "cerrar esta pagina" in user_message_lower or "cerrar pagina" in user_message_lower:
1322
+ action_desc = "¡Listo, cerrando la página actual en el navegador simulado!"
1323
+ new_url = "about:blank" # Simula una página en blanco o cerrada
1324
+ chat_history.append((user_message, action_desc))
1325
+ return chat_history, "", conversation_context_state, action_desc, new_url, gr.HTML.update(value=simulate_browser_action(action_desc, new_url)[2].value)
1326
 
1327
  # Lógica para pedir un susurro contextual
1328
  current_step = conversation_context_state["step"]
 
1350
  chat_history.append((user_message, response))
1351
  # Reiniciar contexto después de generar el nudge
1352
  conversation_context_state = {"step": "initial", "pending_nudge_request": False, "location": None, "activity": None, "sentiment": None}
1353
+ return chat_history, "", conversation_context_state, response, gr.skip(), gr.skip()
1354
 
1355
  elif current_step == "ask_location":
1356
  if any(k in user_message_lower for k in ["casa", "hogar"]):
 
1361
  location = "Aire Libre/Calle"
1362
  else:
1363
  chat_history.append((user_message, "No te entendí bien, che. ¿Estás en casa, en la oficina o en la calle?"))
1364
+ return chat_history, "", conversation_context_state, "", gr.skip(), gr.skip()
1365
 
1366
  conversation_context_state["location"] = location
1367
  if not activity:
 
1375
  response = nudge_text
1376
  chat_history.append((user_message, response))
1377
  conversation_context_state = {"step": "initial", "pending_nudge_request": False, "location": None, "activity": None, "sentiment": None}
1378
+ return chat_history, "", conversation_context_state, response, gr.skip(), gr.skip()
1379
 
1380
  elif current_step == "ask_activity":
1381
  if any(k in user_message_lower for k in ["relajado", "tranqui", "descansando"]):
 
1392
  activity = "Viendo un partido"
1393
  else:
1394
  chat_history.append((user_message, "No te pesqué esa, che. ¿Estás laburando, haciendo ejercicio, relajado, cocinando, o viendo un partido?"))
1395
+ return chat_history, "", conversation_context_state, "", gr.skip(), gr.skip()
1396
 
1397
  conversation_context_state["activity"] = activity
1398
  if not sentiment:
 
1403
  response = nudge_text
1404
  chat_history.append((user_message, response))
1405
  conversation_context_state = {"step": "initial", "pending_nudge_request": False, "location": None, "activity": None, "sentiment": None}
1406
+ return chat_history, "", conversation_context_state, response, gr.skip(), gr.skip()
1407
 
1408
  elif current_step == "ask_sentiment":
1409
  if any(k in user_message_lower for k in ["bien", "joya", "diez puntos", "contento"]):
 
1416
  sentiment = "Motivado"
1417
  else:
1418
  chat_history.append((user_message, "No te capto el sentimiento, che. ¿Estás bien, cansado, bajoneado o motivado?"))
1419
+ return chat_history, "", conversation_context_state, "", gr.skip(), gr.skip()
1420
 
1421
  conversation_context_state["sentiment"] = sentiment
1422
  # Ahora que tenemos todo, generar el nudge
 
1425
  chat_history.append((user_message, response))
1426
  # Reiniciar contexto después de generar el nudge
1427
  conversation_context_state = {"step": "initial", "pending_nudge_request": False, "location": None, "activity": None, "sentiment": None}
1428
+ return chat_history, "", conversation_context_state, response, gr.skip(), gr.skip()
1429
 
1430
  else: # Default response if no specific command or context step
1431
  response = "¡No te entendí bien, che! Soy MateAI, tu compañero. Si querés un susurro, decime 'quiero un susurro' y te voy a preguntar unas cositas. Si no, decime qué andás necesitando."
1432
  chat_history.append((user_message, response))
1433
+ return chat_history, "", conversation_context_state, response, gr.skip(), gr.skip()
 
 
 
1434
 
1435
  # --- Conexión de Eventos ---
1436
  btn_crear_usuario.click(
 
1451
  outputs=[output_actualizar_preferencias, current_user_state]
1452
  )
1453
 
 
 
 
 
 
 
 
 
1454
  btn_oraculo.click(
1455
  fn=_get_oracle_revelation_gradio,
1456
  inputs=[current_user_state],
 
1481
  outputs=gr.File(label="Descargar tu Diario")
1482
  )
1483
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1484
  # --- Manejo de la entrada de voz y texto conversacional ---
1485
  # El input de texto (msg_input) y el botón de enviar (btn_send_msg)
1486
  # ahora se conectan a la función _process_conversational_input
1487
  msg_input.submit(
1488
  fn=_process_conversational_input,
1489
  inputs=[msg_input, chat_history, current_user_state, conversation_context_state],
1490
+ outputs=[chatbot, msg_input, conversation_context_state, voice_output_text, simulated_action_output, simulated_browser_url, simulated_browser_content]
1491
  )
1492
  btn_send_msg.click(
1493
  fn=_process_conversational_input,
1494
  inputs=[msg_input, chat_history, current_user_state, conversation_context_state],
1495
+ outputs=[chatbot, msg_input, conversation_context_state, voice_output_text, simulated_action_output, simulated_browser_url, simulated_browser_content]
1496
  )
1497
 
1498
  # El botón oculto para la entrada de voz también se conecta a la función conversacional
1499
  hidden_voice_submit_button.click(
1500
  fn=_process_conversational_input,
1501
  inputs=[voice_input_textbox, chat_history, current_user_state, conversation_context_state],
1502
+ outputs=[chatbot, voice_input_textbox, conversation_context_state, voice_output_text, simulated_action_output, simulated_browser_url, simulated_browser_content]
1503
  )
1504
 
1505
  # Cuando el backend genera texto para voz, lo envía a voice_output_text,
 
1511
  js="text => window.speakText(text)"
1512
  )
1513
 
1514
+ # --- Conexión del botón oculto de cambio de vista ---
1515
  all_sections = [
1516
  "home_section", "oracle_section", "nudges_section",
1517
  "challenges_section", "history_section", "tasks_section",
1518
+ "browser_agent_section" # Actualizado
1519
  ]
1520
 
1521
+ hidden_view_changer_button.click(
1522
+ fn=change_view,
1523
+ inputs=[hidden_view_input],
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1524
  outputs=[gr.Column(elem_id=s) for s in all_sections] + [current_view_state]
1525
  )
1526