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

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +196 -145
app.py CHANGED
@@ -34,36 +34,17 @@ class Config:
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
 
@@ -74,7 +55,7 @@ db = firestore.client() if firebase_admin._apps else None # Solo si Firebase se
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)
@@ -88,7 +69,7 @@ class UserManager:
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()
@@ -386,7 +367,7 @@ class NudgeGenerator:
386
  if not user_history:
387
  return nudges
388
 
389
- recent_nudges_text = user_history[-5:] # Evitar los últimos 5
390
  filtered_nudges = [n for n in nudges if n.text_template not in recent_nudges_text] # Usar template para evitar repeticiones
391
  return filtered_nudges if filtered_nudges else nudges # Si no hay nuevos, repite (mejor que nada)
392
 
@@ -403,7 +384,7 @@ class NudgeGenerator:
403
  current_points = user.eco_points
404
  current_insignia = gamification_engine.get_insignia(current_points)
405
  next_insignia_goal = gamification_engine.get_next_insignia_goal(current_points)
406
- historial = "\n".join(user.nudge_history)
407
  # El mensaje de cooldown también debe ser vocalizado
408
  cooldown_msg = f"MateAI necesita un respiro. Volvé a pedir un susurro en {remaining_time.seconds} segundos. ¡La paciencia es una virtud!"
409
  return cooldown_msg, str(current_points), current_insignia, next_insignia_goal, historial
@@ -447,7 +428,7 @@ class NudgeGenerator:
447
  break
448
 
449
  user.eco_points += Config.ECO_PUNTOS_POR_SUSURRO
450
- user.nudge_history.append(formatted_nudge_text)
451
  if len(user.nudge_history) > Config.MAX_HISTORIAL_SUSURROS:
452
  user.nudge_history.pop(0)
453
  user.last_nudge_time = datetime.now()
@@ -455,7 +436,7 @@ class NudgeGenerator:
455
  current_points = user.eco_points
456
  current_insignia = gamification_engine.get_insignia(current_points)
457
  next_insignia_goal = gamification_engine.get_next_insignia_goal(current_points)
458
- historial = "\n".join(user.nudge_history)
459
  return formatted_nudge_text, str(current_points), current_insignia, next_insignia_goal, historial
460
 
461
  # --- Si no hay tareas pendientes, proceder con nudges generales ---
@@ -489,7 +470,7 @@ class NudgeGenerator:
489
  )
490
 
491
  user.eco_points += Config.ECO_PUNTOS_POR_SUSURRO
492
- user.nudge_history.append(formatted_nudge_text) # Guardar el texto formateado en el historial
493
  if len(user.nudge_history) > Config.MAX_HISTORIAL_SUSURROS:
494
  user.nudge_history.pop(0)
495
  user.last_nudge_time = datetime.now()
@@ -499,7 +480,7 @@ class NudgeGenerator:
499
  current_points = user.eco_points
500
  current_insignia = gamification_engine.get_insignia(current_points)
501
  next_insignia_goal = gamification_engine.get_next_insignia_goal(current_points)
502
- historial = "\n".join(user.nudge_history)
503
  return formatted_nudge_text, str(current_points), current_insignia, next_insignia_goal, historial
504
 
505
  async def get_daily_oracle_revelation(self, user_id):
@@ -774,16 +755,28 @@ with demo: # Ahora el bloque 'with' utiliza el objeto demo ya creado
774
  let lastSpokenText = "";
775
  const matecitoAvatar = document.getElementById('matecito_avatar');
776
  const voiceStatus = document.getElementById('voice_status');
 
777
 
778
- // Función para inicializar y controlar el reconocimiento de voz
779
  function startListening() {
 
 
 
 
 
 
 
 
780
  if (recognition) {
781
  recognition.stop();
 
782
  }
 
783
  recognition = new (window.SpeechRecognition || window.webkitSpeechRecognition)();
784
- recognition.lang = 'es-AR'; // Español de Argentina
785
  recognition.interimResults = false;
786
  recognition.maxAlternatives = 1;
 
787
 
788
  recognition.onstart = function() {
789
  voiceStatus.textContent = 'Escuchando... 🎙️';
@@ -791,12 +784,13 @@ with demo: # Ahora el bloque 'with' utiliza el objeto demo ya creado
791
  matecitoAvatar.style.display = 'block';
792
  matecitoAvatar.classList.add('matecito-listening');
793
  matecitoAvatar.classList.remove('matecito-talking');
 
794
  console.log('Voice recognition started.');
795
  };
796
 
797
  recognition.onresult = function(event) {
798
  const transcript = event.results[0][0].transcript;
799
- document.getElementById('voice_input_textbox').value = transcript; // Pasa el texto al textbox oculto
800
  voiceStatus.textContent = 'Texto reconocido: ' + transcript;
801
  console.log('Recognized:', transcript);
802
  // Trigger a Gradio event to send this text to Python
@@ -804,13 +798,14 @@ with demo: # Ahora el bloque 'with' utiliza el objeto demo ya creado
804
  };
805
 
806
  recognition.onend = function() {
807
- if (!speaking) { // Solo si no está hablando MateAI
808
  voiceStatus.textContent = 'Listo para hablar 🎤';
809
  voiceStatus.style.color = '#4b5563'; // Gray
810
  matecitoAvatar.classList.remove('matecito-listening');
811
  matecitoAvatar.style.animation = 'fade-out 0.3s forwards';
812
- setTimeout(() => matecitoAvatar.style.display = 'none', 300); // Ocultar después de la animación
813
  }
 
814
  console.log('Voice recognition ended.');
815
  };
816
 
@@ -820,7 +815,8 @@ with demo: # Ahora el bloque 'with' utiliza el objeto demo ya creado
820
  voiceStatus.style.color = '#ef4444'; // Red
821
  matecitoAvatar.classList.remove('matecito-listening');
822
  matecitoAvatar.style.animation = 'fade-out 0.3s forwards';
823
- setTimeout(() => matecitoAvatar.style.display = 'none', 300); // Ocultar después de la animación
 
824
  if (event.error === 'not-allowed' || event.error === 'permission-denied') {
825
  voiceStatus.textContent = 'ERROR: Permiso de micrófono denegado. Por favor, habilítalo en la configuración de tu navegador. ❌';
826
  voiceStatus.style.color = '#ef4444';
@@ -828,7 +824,7 @@ with demo: # Ahora el bloque 'with' utiliza el objeto demo ya creado
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
  };
@@ -836,15 +832,15 @@ with demo: # Ahora el bloque 'with' utiliza el objeto demo ya creado
836
  recognition.start();
837
  }
838
 
839
- // Función para que MateAI hable
840
  function speakText(text) {
841
- if (!text || text === lastSpokenText) return; // Evita repetir el mismo texto
842
  lastSpokenText = text;
843
 
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' &&
@@ -868,6 +864,7 @@ with demo: # Ahora el bloque 'with' utiliza el objeto demo ya creado
868
  matecitoAvatar.classList.add('matecito-talking');
869
  matecitoAvatar.classList.remove('matecito-listening');
870
  matecitoAvatar.style.animation = 'fade-in 0.3s forwards';
 
871
  };
872
 
873
  utterance.onend = function() {
@@ -876,30 +873,32 @@ with demo: # Ahora el bloque 'with' utiliza el objeto demo ya creado
876
  voiceStatus.style.color = '#4b5563'; // Gray
877
  matecitoAvatar.classList.remove('matecito-talking');
878
  matecitoAvatar.style.animation = 'fade-out 0.3s forwards';
879
- setTimeout(() => matecitoAvatar.style.display = 'none', 300); // Ocultar después de la animación
880
- // Si el reconocimiento estaba activo antes de hablar, reiniciarlo
881
- if (recognition && recognition.continuous) { // Si se desea escucha continua después de hablar
882
- recognition.start();
883
- }
 
884
  };
885
 
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';
893
- setTimeout(() => matecitoAvatar.style.display = 'none', 300); // Ocultar después de la animación
 
894
  };
895
 
896
  window.speechSynthesis.speak(utterance);
897
  }
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');
@@ -908,7 +907,7 @@ with demo: # Ahora el bloque 'with' utiliza el objeto demo ya creado
908
  hiddenButton.click();
909
  }
910
  }
911
- window.changeGradioView = changeGradioView; // Exponer globalmente
912
  </script>
913
  """)
914
  # Input oculto para el texto reconocido por voz
@@ -965,14 +964,22 @@ with demo: # Ahora el bloque 'with' utiliza el objeto demo ya creado
965
  with gr.Column(elem_id="nudges_section", elem_classes="section-content", visible=False):
966
  gr.Markdown("<h2 style='color: #10b981;'>¡Che, acá estoy para cebarte un susurro!</h2><p>Contame un poco de tu día y te tiro la posta.</p>")
967
  chat_history = gr.State([]) # Para mantener el historial del chat
968
- chatbot = gr.Chatbot(label="Conversación con MateAI", height=300)
969
  msg_input = gr.Textbox(label="Contale a MateAI...", placeholder="Ej: 'Estoy en casa, laburando y medio cansado.' o 'Quiero un susurro de bienestar.'")
970
 
971
  # Botón para enviar el mensaje de texto (además de la entrada de voz)
972
  btn_send_msg = gr.Button("Enviar Mensaje", variant="primary")
973
 
974
  # Resto de las secciones (ocultas por defecto)
975
- with gr.Column(elem_id="home_section", elem_classes="section-content", visible=True): # Home visible por defecto
 
 
 
 
 
 
 
 
976
  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>")
977
  with gr.Row():
978
  with gr.Column():
@@ -1016,7 +1023,7 @@ with demo: # Ahora el bloque 'with' utiliza el objeto demo ya creado
1016
  btn_actualizar_preferencias = gr.Button("Actualizar Preferencias", variant="primary")
1017
  output_actualizar_preferencias = gr.Textbox(label="Estado de Actualización", interactive=False)
1018
 
1019
- with gr.Column(elem_id="oracle_section", elem_classes="section-content", visible=False):
1020
  gr.Markdown("<h2 style='text-align: center; color: #10b981;'>✨ La Revelación Diaria del Oráculo MateAI ✨</h2><p style='text-align: center;'>Una sabiduría profunda para iluminar tu jornada. El Oráculo te habla una vez al día.</p>")
1021
  oraculo_output = gr.Textbox(
1022
  label="La Revelación del Oráculo",
@@ -1028,7 +1035,7 @@ with demo: # Ahora el bloque 'with' utiliza el objeto demo ya creado
1028
  btn_oraculo = gr.Button("¡Consultar al Oráculo MateAI!", variant="primary")
1029
  gr.Markdown("<p style='text-align: center; font-size: 0.9em; color: #6b7280; margin-top: 1em;'><i>El Oráculo te hablará una vez por día. Volvé mañana para una nueva revelación.</i></p>")
1030
 
1031
- with gr.Column(elem_id="challenges_section", elem_classes="section-content", visible=False):
1032
  gr.Markdown("<h2 style='text-align: center; color: #10b981;'>🎯 Desafíos MateAI: ¡Ponete a Prueba! 🎯</h2><p style='text-align: center;'>MateAI te propone desafíos para crecer en bienestar y sostenibilidad. ¡Aceptá la misión!</p>")
1033
  desafio_output = gr.Textbox(
1034
  label="Tu Desafío del Día",
@@ -1039,7 +1046,7 @@ with demo: # Ahora el bloque 'with' utiliza el objeto demo ya creado
1039
  btn_desafio = gr.Button("¡Quiero un Desafío MateAI!", variant="primary")
1040
  gr.Markdown("<p style='text-align: center; font-size: 0.9em; color: #6b7280; margin-top: 1em;'><i>Completar desafíos (registrándolos en tu diario) te dará más Eco-Puntos y te acercará a nuevas insignias.</i></p>")
1041
 
1042
- with gr.Column(elem_id="history_section", elem_classes="section-content", visible=False):
1043
  gr.Markdown("<h2 style='color: #10b981;'>Tu Viaje con MateAI</h2><p>Acá podés ver tus susurros pasados y tus logros.</p>")
1044
  historial_susurros_output = gr.Textbox(label="Tus Últimos Susurros Recibidos", lines=10, interactive=False)
1045
 
@@ -1053,7 +1060,7 @@ with demo: # Ahora el bloque 'with' utiliza el objeto demo ya creado
1053
  btn_descargar_diario = gr.Button("Descargar Diario (sesión actual)", variant="secondary")
1054
  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>")
1055
 
1056
- with gr.Column(elem_id="tasks_section", elem_classes="section-content", visible=False):
1057
  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>")
1058
  tarea_nueva_input = gr.Textbox(label="Nueva Tarea", placeholder="Ej: Comprar yerba, Llamar a mi vieja, Leer un libro")
1059
  btn_agregar_tarea = gr.Button("Agregar Tarea", variant="primary")
@@ -1066,7 +1073,7 @@ with demo: # Ahora el bloque 'with' utiliza el objeto demo ya creado
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>
@@ -1134,18 +1141,32 @@ with demo: # Ahora el bloque 'with' utiliza el objeto demo ya creado
1134
 
1135
  # --- Funciones de Interacción para Gradio ---
1136
 
1137
- # Función para cambiar la visibilidad de las secciones
1138
  def change_view(view_name):
1139
- return {
1140
- "home_section": gr.Column.update(visible=view_name == "home"),
1141
- "oracle_section": gr.Column.update(visible=view_name == "oracle"),
1142
- "nudges_section": gr.Column.update(visible=view_name == "nudges"),
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
 
1150
  async def _update_ui_from_user(user_obj):
1151
  """Función auxiliar para actualizar todos los campos de la UI desde un objeto User."""
@@ -1153,7 +1174,7 @@ with demo: # Ahora el bloque 'with' utiliza el objeto demo ya creado
1153
  current_points = user_obj.eco_points
1154
  current_insignia = gamification_engine.get_insignia(current_points)
1155
  next_insignia_goal = gamification_engine.get_next_insignia_goal(current_points)
1156
- historial = "\n".join(user_obj.nudge_history)
1157
  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])
1158
 
1159
  return (user_obj, user_obj.user_id, user_obj.name, str(current_points), current_insignia, next_insignia_goal,
@@ -1171,14 +1192,14 @@ with demo: # Ahora el bloque 'with' utiliza el objeto demo ya creado
1171
  current_points = user.eco_points
1172
  current_insignia = gamification_engine.get_insignia(current_points)
1173
  next_insignia_goal = gamification_engine.get_next_insignia_goal(current_points)
1174
- historial = "\n".join(user.nudge_history)
1175
  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])
1176
 
1177
  # Retorna todos los outputs en el orden correcto para los componentes de Gradio
1178
  return (user, user.user_id, msg, user.name, str(current_points), current_insignia, next_insignia_goal,
1179
  historial, user.preferences.get('tipo_susurro'), user.preferences.get('frecuencia'),
1180
  user.preferences.get('modo_che_tranqui'), tareas_str)
1181
- # Si la creación de usuario falló, retorna valores por defecto y el mensaje de error
1182
  return (None, "No logueado", msg, "", "", "", "", "", Config.DEFAULT_USER_PREFS['tipo_susurro'],
1183
  Config.DEFAULT_USER_PREFS['frecuencia'], Config.DEFAULT_USER_PREFS['modo_che_tranqui'], "")
1184
 
@@ -1189,14 +1210,14 @@ with demo: # Ahora el bloque 'with' utiliza el objeto demo ya creado
1189
  current_points = user.eco_points
1190
  current_insignia = gamification_engine.get_insignia(current_points)
1191
  next_insignia_goal = gamification_engine.get_next_insignia_goal(current_points)
1192
- historial = "\n".join(user.nudge_history)
1193
  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])
1194
 
1195
  # Retorna todos los outputs en el orden correcto para los componentes de Gradio
1196
  return (user, user.user_id, msg, user.name, str(current_points), current_insignia, next_insignia_goal,
1197
  historial, user.preferences.get('tipo_susurro'), user.preferences.get('frecuencia'),
1198
  user.preferences.get('modo_che_tranqui'), tareas_str)
1199
- # Si la carga de usuario falló, retorna valores por defecto y el mensaje de error
1200
  return (None, "No logueado", msg, "", "", "", "", "", Config.DEFAULT_USER_PREFS['tipo_susurro'],
1201
  Config.DEFAULT_USER_PREFS['frecuencia'], Config.DEFAULT_USER_PREFS['modo_che_tranqui'], "")
1202
 
@@ -1218,27 +1239,45 @@ with demo: # Ahora el bloque 'with' utiliza el objeto demo ya creado
1218
  if not user_obj:
1219
  return "Por favor, crea o carga un usuario primero para consultar al Oráculo.", ""
1220
  revelation_text = await nudge_generator.get_daily_oracle_revelation(user_obj.user_id)
1221
- return revelation_text, revelation_text # Retorna el texto para vocalizar
1222
 
1223
  async def _get_mateai_challenge_gradio(user_obj):
1224
  if not user_obj:
1225
  return "Por favor, crea o carga un usuario primero para recibir desafíos.", ""
1226
  challenge_text = await nudge_generator.get_mateai_challenge(user_obj.user_id)
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")
@@ -1250,81 +1289,92 @@ with demo: # Ahora el bloque 'with' utiliza el objeto demo ya creado
1250
  user_obj = current_user_state
1251
 
1252
  if not user_obj:
1253
- chat_history.append((user_message, "¡Che, para arrancar, necesito que crees o cargues tu perfil en la sección 'Inicio & Perfil'! Después volvé acá y seguimos charlando."))
1254
- return chat_history, "", conversation_context_state, ""
 
1255
 
1256
- # Manejo de comandos directos para otras secciones (simula la voz controlando el menú)
 
 
 
1257
  if "oráculo" in user_message_lower or "revelación" in user_message_lower:
1258
  revelation_text = await nudge_generator.get_daily_oracle_revelation(user_obj.user_id)
1259
- chat_history.append((user_message, revelation_text))
1260
- return chat_history, "", conversation_context_state, revelation_text
1261
 
1262
  if "desafío" in user_message_lower or "reto" in user_message_lower:
1263
  challenge_text = await nudge_generator.get_mateai_challenge(user_obj.user_id)
1264
- chat_history.append((user_message, challenge_text))
1265
- return chat_history, "", conversation_context_state, challenge_text
1266
 
1267
  if "agregar tarea" in user_message_lower or "nueva tarea" in user_message_lower:
1268
  task_name = user_message_lower.replace("agregar tarea", "").replace("nueva tarea", "").strip()
1269
  if task_name:
1270
  msg, _ = await nudge_generator.add_task(user_obj.user_id, task_name)
1271
- chat_history.append((user_message, msg))
1272
- return chat_history, "", conversation_context_state, msg
1273
  else:
1274
- chat_history.append((user_message, "¡Dale, che! ¿Qué tarea querés que agregue? Decime, por ejemplo: 'agregar tarea comprar yerba'."))
1275
- return chat_history, "", conversation_context_state, "¡Dale, che! ¿Qué tarea querés que agregue? Decime, por ejemplo: 'agregar tarea comprar yerba'."
 
1276
 
1277
  if "completar tarea" in user_message_lower or "tarea lista" in user_message_lower:
1278
  task_name = user_message_lower.replace("completar tarea", "").replace("tarea lista", "").strip()
1279
  if task_name:
1280
  msg, _ = await nudge_generator.complete_task(user_obj.user_id, task_name)
1281
- chat_history.append((user_message, msg))
1282
- return chat_history, "", conversation_context_state, msg
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"]
1329
  location = conversation_context_state["location"]
1330
  activity = conversation_context_state["activity"]
@@ -1344,13 +1394,13 @@ with demo: # Ahora el bloque 'with' utiliza el objeto demo ya creado
1344
  response = f"Entendido, en {location} y {activity}. ¿Y cómo te sentís? ¿Bien, cansado, bajoneado, motivado?"
1345
  conversation_context_state["step"] = "ask_sentiment"
1346
  else:
1347
- # Ya tenemos todo, generar el nudge
1348
  nudge_text, current_points, current_insignia, next_insignia_goal, historial = await nudge_generator.generate_nudge(user_obj.user_id, location, activity, sentiment)
1349
  response = nudge_text
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"]):
@@ -1360,8 +1410,9 @@ with demo: # Ahora el bloque 'with' utiliza el objeto demo ya creado
1360
  elif any(k in user_message_lower for k in ["calle", "aire libre", "plaza", "parque", "caminando"]):
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:
@@ -1373,9 +1424,9 @@ with demo: # Ahora el bloque 'with' utiliza el objeto demo ya creado
1373
  else: # Should not happen if flow is sequential, but as fallback
1374
  nudge_text, current_points, current_insignia, next_insignia_goal, historial = await nudge_generator.generate_nudge(user_obj.user_id, location, activity, sentiment)
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"]):
@@ -1391,8 +1442,9 @@ with demo: # Ahora el bloque 'with' utiliza el objeto demo ya creado
1391
  elif any(k in user_message_lower for k in ["partido", "fútbol", "viendo tele"]):
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:
@@ -1401,9 +1453,9 @@ with demo: # Ahora el bloque 'with' utiliza el objeto demo ya creado
1401
  else: # Should not happen if flow is sequential, but as fallback
1402
  nudge_text, current_points, current_insignia, next_insignia_goal, historial = await nudge_generator.generate_nudge(user_obj.user_id, location, activity, 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"]):
@@ -1415,22 +1467,23 @@ with demo: # Ahora el bloque 'with' utiliza el objeto demo ya creado
1415
  elif any(k in user_message_lower for k in ["motivado", "con pilas", "enchufado"]):
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
1423
  nudge_text, current_points, current_insignia, next_insignia_goal, historial = await nudge_generator.generate_nudge(user_obj.user_id, location, activity, sentiment)
1424
  response = nudge_text
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(
@@ -1502,26 +1555,24 @@ with demo: # Ahora el bloque 'with' utiliza el objeto demo ya creado
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,
1506
- # y el JS lo vocaliza.
1507
  voice_output_text.change(
1508
- fn=None, # No hay función Python, solo se usa para activar JS
1509
  inputs=[voice_output_text],
1510
  outputs=None,
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
 
1527
  # Lanza la interfaz de Gradio
 
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
+ # Intenta cargar las credenciales desde la variable de entorno GOOGLE_APPLICATION_CREDENTIALS_JSON
38
+ firebase_credentials_json = os.getenv('GOOGLE_APPLICATION_CREDENTIALS_JSON', None)
39
+ if firebase_credentials_json:
40
+ cred = credentials.Certificate(json.loads(firebase_credentials_json))
41
  firebase_admin.initialize_app(cred)
42
+ print("Firebase Admin SDK inicializado correctamente desde variable de entorno GOOGLE_APPLICATION_CREDENTIALS_JSON.")
43
  else:
44
+ # Si no se encuentra la variable de entorno, informa al usuario.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
45
  print("ADVERTENCIA: GOOGLE_APPLICATION_CREDENTIALS_JSON no está configurada y no se pudo inicializar Firebase Admin SDK.")
46
  print("La persistencia de datos de usuario en Firestore NO funcionará.")
47
  # No inicializamos firebase_admin si no tenemos credenciales de servicio válidas.
 
 
48
  except ValueError:
49
  pass # Ya inicializado
50
 
 
55
  class UserManager:
56
  @classmethod
57
  async def create_user(cls, name, initial_prefs=None):
58
+ if not db: return None, "Error: La base de datos no está disponible. Por favor, configura Firebase Admin SDK."
59
  user_id = f"user_{int(time.time())}_{random.randint(1000, 9999)}" # ID único basado en tiempo y random
60
  prefs = initial_prefs if initial_prefs else Config.DEFAULT_USER_PREFS.copy()
61
  new_user = User(user_id, name, prefs)
 
69
 
70
  @classmethod
71
  async def login_user(cls, user_id):
72
+ if not db: return None, "Error: La base de datos no está disponible. Por favor, configura Firebase Admin SDK."
73
  user_doc_ref = db.collection(Config.FIREBASE_COLLECTION_USERS).document(user_id)
74
  try:
75
  doc = user_doc_ref.get()
 
367
  if not user_history:
368
  return nudges
369
 
370
+ recent_nudges_text = [entry['content'] for entry in user_history if entry['role'] == 'MateAI'][-5:] # Evitar los últimos 5
371
  filtered_nudges = [n for n in nudges if n.text_template not in recent_nudges_text] # Usar template para evitar repeticiones
372
  return filtered_nudges if filtered_nudges else nudges # Si no hay nuevos, repite (mejor que nada)
373
 
 
384
  current_points = user.eco_points
385
  current_insignia = gamification_engine.get_insignia(current_points)
386
  next_insignia_goal = gamification_engine.get_next_insignia_goal(current_points)
387
+ historial = "\n".join([entry['content'] for entry in user.nudge_history if entry['role'] == 'MateAI'])
388
  # El mensaje de cooldown también debe ser vocalizado
389
  cooldown_msg = f"MateAI necesita un respiro. Volvé a pedir un susurro en {remaining_time.seconds} segundos. ¡La paciencia es una virtud!"
390
  return cooldown_msg, str(current_points), current_insignia, next_insignia_goal, historial
 
428
  break
429
 
430
  user.eco_points += Config.ECO_PUNTOS_POR_SUSURRO
431
+ user.nudge_history.append({"role": "MateAI", "content": formatted_nudge_text}) # Guardar como diccionario
432
  if len(user.nudge_history) > Config.MAX_HISTORIAL_SUSURROS:
433
  user.nudge_history.pop(0)
434
  user.last_nudge_time = datetime.now()
 
436
  current_points = user.eco_points
437
  current_insignia = gamification_engine.get_insignia(current_points)
438
  next_insignia_goal = gamification_engine.get_next_insignia_goal(current_points)
439
+ historial = "\n".join([entry['content'] for entry in user.nudge_history if entry['role'] == 'MateAI'])
440
  return formatted_nudge_text, str(current_points), current_insignia, next_insignia_goal, historial
441
 
442
  # --- Si no hay tareas pendientes, proceder con nudges generales ---
 
470
  )
471
 
472
  user.eco_points += Config.ECO_PUNTOS_POR_SUSURRO
473
+ user.nudge_history.append({"role": "MateAI", "content": formatted_nudge_text}) # Guardar como diccionario
474
  if len(user.nudge_history) > Config.MAX_HISTORIAL_SUSURROS:
475
  user.nudge_history.pop(0)
476
  user.last_nudge_time = datetime.now()
 
480
  current_points = user.eco_points
481
  current_insignia = gamification_engine.get_insignia(current_points)
482
  next_insignia_goal = gamification_engine.get_next_insignia_goal(current_points)
483
+ historial = "\n".join([entry['content'] for entry in user.nudge_history if entry['role'] == 'MateAI'])
484
  return formatted_nudge_text, str(current_points), current_insignia, next_insignia_goal, historial
485
 
486
  async def get_daily_oracle_revelation(self, user_id):
 
755
  let lastSpokenText = "";
756
  const matecitoAvatar = document.getElementById('matecito_avatar');
757
  const voiceStatus = document.getElementById('voice_status');
758
+ const startVoiceButton = document.getElementById('start_voice_button'); // Get the button element
759
 
760
+ // Function to initialize and control voice recognition
761
  function startListening() {
762
+ // Check for browser compatibility
763
+ if (!('SpeechRecognition' in window || 'webkitSpeechRecognition' in window)) {
764
+ voiceStatus.textContent = 'ERROR: Tu navegador no soporta reconocimiento de voz. Probá con Chrome o Edge. ❌';
765
+ voiceStatus.style.color = '#ef4444';
766
+ console.error('Speech Recognition API not supported in this browser.');
767
+ return;
768
+ }
769
+
770
  if (recognition) {
771
  recognition.stop();
772
+ recognition = null; // Reset recognition object
773
  }
774
+
775
  recognition = new (window.SpeechRecognition || window.webkitSpeechRecognition)();
776
+ recognition.lang = 'es-AR'; // Argentine Spanish
777
  recognition.interimResults = false;
778
  recognition.maxAlternatives = 1;
779
+ recognition.continuous = false; // Set to false to stop after a single utterance
780
 
781
  recognition.onstart = function() {
782
  voiceStatus.textContent = 'Escuchando... 🎙️';
 
784
  matecitoAvatar.style.display = 'block';
785
  matecitoAvatar.classList.add('matecito-listening');
786
  matecitoAvatar.classList.remove('matecito-talking');
787
+ startVoiceButton.disabled = true; // Disable button while listening
788
  console.log('Voice recognition started.');
789
  };
790
 
791
  recognition.onresult = function(event) {
792
  const transcript = event.results[0][0].transcript;
793
+ document.getElementById('voice_input_textbox').value = transcript; // Pass text to hidden textbox
794
  voiceStatus.textContent = 'Texto reconocido: ' + transcript;
795
  console.log('Recognized:', transcript);
796
  // Trigger a Gradio event to send this text to Python
 
798
  };
799
 
800
  recognition.onend = function() {
801
+ if (!speaking) { // Only if MateAI is not speaking
802
  voiceStatus.textContent = 'Listo para hablar 🎤';
803
  voiceStatus.style.color = '#4b5563'; // Gray
804
  matecitoAvatar.classList.remove('matecito-listening');
805
  matecitoAvatar.style.animation = 'fade-out 0.3s forwards';
806
+ setTimeout(() => matecitoAvatar.style.display = 'none', 300); // Hide after animation
807
  }
808
+ startVoiceButton.disabled = false; // Re-enable button
809
  console.log('Voice recognition ended.');
810
  };
811
 
 
815
  voiceStatus.style.color = '#ef4444'; // Red
816
  matecitoAvatar.classList.remove('matecito-listening');
817
  matecitoAvatar.style.animation = 'fade-out 0.3s forwards';
818
+ setTimeout(() => matecitoAvatar.style.display = 'none', 300); // Hide after animation
819
+ startVoiceButton.disabled = false; // Re-enable button
820
  if (event.error === 'not-allowed' || event.error === 'permission-denied') {
821
  voiceStatus.textContent = 'ERROR: Permiso de micrófono denegado. Por favor, habilítalo en la configuración de tu navegador. ❌';
822
  voiceStatus.style.color = '#ef4444';
 
824
  voiceStatus.textContent = 'No se detectó voz. Intenta hablar más claro o más fuerte. 🎤';
825
  voiceStatus.style.color = '#fbbf24'; // Amber
826
  } else if (event.error === 'aborted') {
827
+ voiceStatus.textContent = 'Reconocimiento de voz cancelado. ';
828
  voiceStatus.style.color = '#4b5563';
829
  }
830
  };
 
832
  recognition.start();
833
  }
834
 
835
+ // Function for MateAI to speak
836
  function speakText(text) {
837
+ if (!text || text === lastSpokenText) return; // Avoid repeating the same text
838
  lastSpokenText = text;
839
 
840
  const utterance = new SpeechSynthesisUtterance(text);
841
+ utterance.lang = 'es-AR'; // Argentine Spanish
842
 
843
+ // Try to find an Argentine Spanish male voice
844
  const voices = window.speechSynthesis.getVoices();
845
  const preferredVoice = voices.find(voice =>
846
  voice.lang === 'es-AR' &&
 
864
  matecitoAvatar.classList.add('matecito-talking');
865
  matecitoAvatar.classList.remove('matecito-listening');
866
  matecitoAvatar.style.animation = 'fade-in 0.3s forwards';
867
+ startVoiceButton.disabled = true; // Disable button while speaking
868
  };
869
 
870
  utterance.onend = function() {
 
873
  voiceStatus.style.color = '#4b5563'; // Gray
874
  matecitoAvatar.classList.remove('matecito-talking');
875
  matecitoAvatar.style.animation = 'fade-out 0.3s forwards';
876
+ setTimeout(() => matecitoAvatar.style.display = 'none', 300); // Hide after animation
877
+ startVoiceButton.disabled = false; // Re-enable button
878
+ // If recognition was active before speaking, restart it
879
+ // if (recognition && recognition.continuous) { // If continuous listening is desired after speaking
880
+ // recognition.start();
881
+ // }
882
  };
883
 
884
  utterance.onerror = function(event) {
885
  speaking = false;
886
  console.error('Speech synthesis error:', event.error);
887
+ voiceStatus.textContent = 'Error al hablar 🔇';
888
  voiceStatus.style.color = '#ef4444'; // Red
889
  matecitoAvatar.classList.remove('matecito-talking');
890
  matecitoAvatar.style.animation = 'fade-out 0.3s forwards';
891
+ setTimeout(() => matecitoAvatar.style.display = 'none', 300); // Hide after animation
892
+ startVoiceButton.disabled = false; // Re-enable button
893
  };
894
 
895
  window.speechSynthesis.speak(utterance);
896
  }
897
 
898
+ // Expose the speakText function globally for Gradio to call it
899
  window.speakText = speakText;
900
 
901
+ // Function to change Gradio view (called from custom HTML buttons)
902
  function changeGradioView(viewName) {
903
  const hiddenButton = document.getElementById('hidden_view_changer_button');
904
  const viewInput = document.getElementById('hidden_view_input');
 
907
  hiddenButton.click();
908
  }
909
  }
910
+ window.changeGradioView = changeGradioView; // Expose globally
911
  </script>
912
  """)
913
  # Input oculto para el texto reconocido por voz
 
964
  with gr.Column(elem_id="nudges_section", elem_classes="section-content", visible=False):
965
  gr.Markdown("<h2 style='color: #10b981;'>¡Che, acá estoy para cebarte un susurro!</h2><p>Contame un poco de tu día y te tiro la posta.</p>")
966
  chat_history = gr.State([]) # Para mantener el historial del chat
967
+ chatbot = gr.Chatbot(label="Conversación con MateAI", height=300, type='messages') # Added type='messages'
968
  msg_input = gr.Textbox(label="Contale a MateAI...", placeholder="Ej: 'Estoy en casa, laburando y medio cansado.' o 'Quiero un susurro de bienestar.'")
969
 
970
  # Botón para enviar el mensaje de texto (además de la entrada de voz)
971
  btn_send_msg = gr.Button("Enviar Mensaje", variant="primary")
972
 
973
  # Resto de las secciones (ocultas por defecto)
974
+ home_section = gr.Column(elem_id="home_section", elem_classes="section-content", visible=True)
975
+ oracle_section = gr.Column(elem_id="oracle_section", elem_classes="section-content", visible=False)
976
+ challenges_section = gr.Column(elem_id="challenges_section", elem_classes="section-content", visible=False)
977
+ history_section = gr.Column(elem_id="history_section", elem_classes="section-content", visible=False)
978
+ tasks_section = gr.Column(elem_id="tasks_section", elem_classes="section-content", visible=False)
979
+ browser_agent_section = gr.Column(elem_id="browser_agent_section", elem_classes="section-content", visible=False)
980
+ nudges_section = gr.Column(elem_id="nudges_section", elem_classes="section-content", visible=False) # Defined earlier, but need to be included in the outputs list
981
+
982
+ with home_section: # Home visible por defecto
983
  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>")
984
  with gr.Row():
985
  with gr.Column():
 
1023
  btn_actualizar_preferencias = gr.Button("Actualizar Preferencias", variant="primary")
1024
  output_actualizar_preferencias = gr.Textbox(label="Estado de Actualización", interactive=False)
1025
 
1026
+ with oracle_section:
1027
  gr.Markdown("<h2 style='text-align: center; color: #10b981;'>✨ La Revelación Diaria del Oráculo MateAI ✨</h2><p style='text-align: center;'>Una sabiduría profunda para iluminar tu jornada. El Oráculo te habla una vez al día.</p>")
1028
  oraculo_output = gr.Textbox(
1029
  label="La Revelación del Oráculo",
 
1035
  btn_oraculo = gr.Button("¡Consultar al Oráculo MateAI!", variant="primary")
1036
  gr.Markdown("<p style='text-align: center; font-size: 0.9em; color: #6b7280; margin-top: 1em;'><i>El Oráculo te hablará una vez por día. Volvé mañana para una nueva revelación.</i></p>")
1037
 
1038
+ with challenges_section:
1039
  gr.Markdown("<h2 style='text-align: center; color: #10b981;'>🎯 Desafíos MateAI: ¡Ponete a Prueba! 🎯</h2><p style='text-align: center;'>MateAI te propone desafíos para crecer en bienestar y sostenibilidad. ¡Aceptá la misión!</p>")
1040
  desafio_output = gr.Textbox(
1041
  label="Tu Desafío del Día",
 
1046
  btn_desafio = gr.Button("¡Quiero un Desafío MateAI!", variant="primary")
1047
  gr.Markdown("<p style='text-align: center; font-size: 0.9em; color: #6b7280; margin-top: 1em;'><i>Completar desafíos (registrándolos en tu diario) te dará más Eco-Puntos y te acercará a nuevas insignias.</i></p>")
1048
 
1049
+ with history_section:
1050
  gr.Markdown("<h2 style='color: #10b981;'>Tu Viaje con MateAI</h2><p>Acá podés ver tus susurros pasados y tus logros.</p>")
1051
  historial_susurros_output = gr.Textbox(label="Tus Últimos Susurros Recibidos", lines=10, interactive=False)
1052
 
 
1060
  btn_descargar_diario = gr.Button("Descargar Diario (sesión actual)", variant="secondary")
1061
  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>")
1062
 
1063
+ with tasks_section:
1064
  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>")
1065
  tarea_nueva_input = gr.Textbox(label="Nueva Tarea", placeholder="Ej: Comprar yerba, Llamar a mi vieja, Leer un libro")
1066
  btn_agregar_tarea = gr.Button("Agregar Tarea", variant="primary")
 
1073
  btn_completar_tarea = gr.Button("Completar Tarea", variant="secondary")
1074
  output_completar_tarea = gr.Textbox(label="Estado de Completado", interactive=False)
1075
 
1076
+ with browser_agent_section:
1077
  gr.Markdown(
1078
  """
1079
  <h2 style='text-align: center; color: #10b981;'>🌐 MateAI: Tu Asistente Web (Concepto) 🌐</h2>
 
1141
 
1142
  # --- Funciones de Interacción para Gradio ---
1143
 
1144
+ # Function to change the visibility of sections
1145
  def change_view(view_name):
1146
+ # Create a dictionary to hold updates for each section
1147
+ updates = {
1148
+ "home_section": gr.Column.update(visible=False),
1149
+ "oracle_section": gr.Column.update(visible=False),
1150
+ "nudges_section": gr.Column.update(visible=False),
1151
+ "challenges_section": gr.Column.update(visible=False),
1152
+ "history_section": gr.Column.update(visible=False),
1153
+ "tasks_section": gr.Column.update(visible=False),
1154
+ "browser_agent_section": gr.Column.update(visible=False),
1155
  }
1156
+ # Set the selected section to visible
1157
+ updates[view_name + "_section"] = gr.Column.update(visible=True)
1158
+
1159
+ # Return the updates in a consistent order, followed by the state update
1160
+ return (
1161
+ updates["home_section"],
1162
+ updates["oracle_section"],
1163
+ updates["nudges_section"],
1164
+ updates["challenges_section"],
1165
+ updates["history_section"],
1166
+ updates["tasks_section"],
1167
+ updates["browser_agent_section"],
1168
+ view_name # This is the current_view_state output
1169
+ )
1170
 
1171
  async def _update_ui_from_user(user_obj):
1172
  """Función auxiliar para actualizar todos los campos de la UI desde un objeto User."""
 
1174
  current_points = user_obj.eco_points
1175
  current_insignia = gamification_engine.get_insignia(current_points)
1176
  next_insignia_goal = gamification_engine.get_next_insignia_goal(current_points)
1177
+ historial = "\n".join([entry['content'] for entry in user_obj.nudge_history if entry['role'] == 'MateAI'])
1178
  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])
1179
 
1180
  return (user_obj, user_obj.user_id, user_obj.name, str(current_points), current_insignia, next_insignia_goal,
 
1192
  current_points = user.eco_points
1193
  current_insignia = gamification_engine.get_insignia(current_points)
1194
  next_insignia_goal = gamification_engine.get_next_insignia_goal(current_points)
1195
+ historial = "\n".join([entry['content'] for entry in user.nudge_history if entry['role'] == 'MateAI'])
1196
  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])
1197
 
1198
  # Retorna todos los outputs en el orden correcto para los componentes de Gradio
1199
  return (user, user.user_id, msg, user.name, str(current_points), current_insignia, next_insignia_goal,
1200
  historial, user.preferences.get('tipo_susurro'), user.preferences.get('frecuencia'),
1201
  user.preferences.get('modo_che_tranqui'), tareas_str)
1202
+ # If user creation failed, return default values and the error message
1203
  return (None, "No logueado", msg, "", "", "", "", "", Config.DEFAULT_USER_PREFS['tipo_susurro'],
1204
  Config.DEFAULT_USER_PREFS['frecuencia'], Config.DEFAULT_USER_PREFS['modo_che_tranqui'], "")
1205
 
 
1210
  current_points = user.eco_points
1211
  current_insignia = gamification_engine.get_insignia(current_points)
1212
  next_insignia_goal = gamification_engine.get_next_insignia_goal(current_points)
1213
+ historial = "\n".join([entry['content'] for entry in user.nudge_history if entry['role'] == 'MateAI'])
1214
  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])
1215
 
1216
  # Retorna todos los outputs en el orden correcto para los componentes de Gradio
1217
  return (user, user.user_id, msg, user.name, str(current_points), current_insignia, next_insignia_goal,
1218
  historial, user.preferences.get('tipo_susurro'), user.preferences.get('frecuencia'),
1219
  user.preferences.get('modo_che_tranqui'), tareas_str)
1220
+ # If user loading failed, return default values and the error message
1221
  return (None, "No logueado", msg, "", "", "", "", "", Config.DEFAULT_USER_PREFS['tipo_susurro'],
1222
  Config.DEFAULT_USER_PREFS['frecuencia'], Config.DEFAULT_USER_PREFS['modo_che_tranqui'], "")
1223
 
 
1239
  if not user_obj:
1240
  return "Por favor, crea o carga un usuario primero para consultar al Oráculo.", ""
1241
  revelation_text = await nudge_generator.get_daily_oracle_revelation(user_obj.user_id)
1242
+ return revelation_text, revelation_text # Return the text for vocalization
1243
 
1244
  async def _get_mateai_challenge_gradio(user_obj):
1245
  if not user_obj:
1246
  return "Por favor, crea o carga un usuario primero para recibir desafíos.", ""
1247
  challenge_text = await nudge_generator.get_mateai_challenge(user_obj.user_id)
1248
+ return challenge_text, challenge_text # Return the text for vocalization
1249
 
1250
  async def _add_task_gradio(user_obj, task_name):
1251
  user_id = user_obj.user_id if user_obj else None
1252
  if not user_id:
1253
  return "Error: Por favor, crea o carga un usuario primero.", ""
1254
+ user = await self.user_manager.get_user(user_id)
1255
+ if not user:
1256
+ return "Error: Usuario no logueado. Por favor, crea o carga un usuario.", ""
1257
+
1258
+ new_task = {"task": task_name, "added_at": datetime.now().strftime('%Y-%m-%d %H:%M:%S.%f'), "last_nudged": None}
1259
+ user.tasks.append(new_task)
1260
+ await self.user_manager.update_user_data(user)
1261
+ 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])
1262
+ return f"¡Tarea '{task_name}' agregada a tu lista, {user.name}!", tareas_str
1263
 
1264
  async def _complete_task_gradio(user_obj, task_name):
1265
  user_id = user_obj.user_id if user_obj else None
1266
  if not user_id:
1267
  return "Error: Por favor, crea o carga un usuario primero.", ""
1268
+ user = await self.user_manager.get_user(user_id)
1269
+ if not user:
1270
+ return "Error: Usuario no logueado. Por favor, crea o carga un usuario.", ""
1271
+
1272
+ initial_task_count = len(user.tasks)
1273
+ user.tasks = [task for task in user.tasks if task['task'].lower() != task_name.lower()]
1274
+
1275
+ if len(user.tasks) < initial_task_count:
1276
+ await self.user_manager.update_user_data(user)
1277
+ 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])
1278
+ return f"¡Tarea '{task_name}' marcada como completada, {user.name}! ¡Bien ahí!", tareas_str
1279
+ 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])
1280
+ return f"No encontré la tarea '{task_name}' en tu lista, {user.name}.", tareas_str
1281
 
1282
  def _download_diary_gradio(diary_text):
1283
  return gr.File(value=diary_text.encode('utf-8'), filename="diario_mateai.txt", type="bytes")
 
1289
  user_obj = current_user_state
1290
 
1291
  if not user_obj:
1292
+ chat_history.append({"role": "user", "content": user_message})
1293
+ chat_history.append({"role": "MateAI", "content": "¡Che, para arrancar, necesito que crees o cargues tu perfil en la sección 'Inicio & Perfil'! Después volvé acá y seguimos charlando."})
1294
+ return chat_history, "", conversation_context_state, "¡Che, para arrancar, necesito que crees o cargues tu perfil en la sección 'Inicio & Perfil'! Después volvé acá y seguimos charlando.", gr.skip(), gr.skip(), gr.skip()
1295
 
1296
+ # Append user message to chat history
1297
+ chat_history.append({"role": "user", "content": user_message})
1298
+
1299
+ # Command handling for other sections (simulates voice controlling the menu)
1300
  if "oráculo" in user_message_lower or "revelación" in user_message_lower:
1301
  revelation_text = await nudge_generator.get_daily_oracle_revelation(user_obj.user_id)
1302
+ chat_history.append({"role": "MateAI", "content": revelation_text})
1303
+ return chat_history, "", conversation_context_state, revelation_text, gr.skip(), gr.skip(), gr.skip()
1304
 
1305
  if "desafío" in user_message_lower or "reto" in user_message_lower:
1306
  challenge_text = await nudge_generator.get_mateai_challenge(user_obj.user_id)
1307
+ chat_history.append({"role": "MateAI", "content": challenge_text})
1308
+ return chat_history, "", conversation_context_state, challenge_text, gr.skip(), gr.skip(), gr.skip()
1309
 
1310
  if "agregar tarea" in user_message_lower or "nueva tarea" in user_message_lower:
1311
  task_name = user_message_lower.replace("agregar tarea", "").replace("nueva tarea", "").strip()
1312
  if task_name:
1313
  msg, _ = await nudge_generator.add_task(user_obj.user_id, task_name)
1314
+ chat_history.append({"role": "MateAI", "content": msg})
1315
+ return chat_history, "", conversation_context_state, msg, gr.skip(), gr.skip(), gr.skip()
1316
  else:
1317
+ response = "¡Dale, che! ¿Qué tarea querés que agregue? Decime, por ejemplo: 'agregar tarea comprar yerba'."
1318
+ chat_history.append({"role": "MateAI", "content": response})
1319
+ return chat_history, "", conversation_context_state, response, gr.skip(), gr.skip(), gr.skip()
1320
 
1321
  if "completar tarea" in user_message_lower or "tarea lista" in user_message_lower:
1322
  task_name = user_message_lower.replace("completar tarea", "").replace("tarea lista", "").strip()
1323
  if task_name:
1324
  msg, _ = await nudge_generator.complete_task(user_obj.user_id, task_name)
1325
+ chat_history.append({"role": "MateAI", "content": msg})
1326
+ return chat_history, "", conversation_context_state, msg, gr.skip(), gr.skip(), gr.skip()
1327
  else:
1328
+ response = "¡Decime qué tarea completaste, así la saco de la lista! Por ejemplo: 'completar tarea ir al súper'."
1329
+ chat_history.append({"role": "MateAI", "content": response})
1330
+ return chat_history, "", conversation_context_state, response, gr.skip(), gr.skip(), gr.skip()
1331
 
1332
+ # --- Conversational Browser Agent (Simulated) ---
1333
  if "abrir" in user_message_lower or "ir a" in user_message_lower or "navegar" in user_message_lower:
1334
  if "google" in user_message_lower:
1335
  action_desc = "¡Dale, abriendo Google en el navegador simulado!"
1336
  new_url = "https://www.google.com.ar"
1337
+ chat_history.append({"role": "MateAI", "content": action_desc})
1338
+ action_output, browser_url, browser_content = simulate_browser_action(action_desc, new_url)
1339
+ return chat_history, "", conversation_context_state, action_output, browser_url, browser_content
1340
  elif "wikipedia" in user_message_lower:
1341
  action_desc = "¡Perfecto! Abriendo Wikipedia en el navegador simulado."
1342
  new_url = "https://es.wikipedia.org"
1343
+ chat_history.append({"role": "MateAI", "content": action_desc})
1344
+ action_output, browser_url, browser_content = simulate_browser_action(action_desc, new_url)
1345
+ return chat_history, "", conversation_context_state, action_output, browser_url, browser_content
1346
  elif "pagina de inicio" in user_message_lower or "inicio" in user_message_lower:
1347
  action_desc = "Volviendo a la página de inicio del navegador simulado."
1348
+ new_url = "https://www.google.com.ar" # Or your default URL
1349
+ chat_history.append({"role": "MateAI", "content": action_desc})
1350
+ action_output, browser_url, browser_content = simulate_browser_action(action_desc, new_url)
1351
+ return chat_history, "", conversation_context_state, action_output, browser_url, browser_content
1352
  else:
1353
  response = "¡Che, a qué página querés ir? Decime, por ejemplo: 'abrir Google' o 'ir a Wikipedia'."
1354
+ chat_history.append({"role": "MateAI", "content": response})
1355
+ return chat_history, "", conversation_context_state, response, gr.skip(), gr.skip(), gr.skip()
1356
 
1357
  if "buscar" in user_message_lower and "en google" in user_message_lower:
1358
  query = user_message_lower.replace("buscar", "").replace("en google", "").strip()
1359
  if query:
1360
  action_desc = f"¡Buscando '{query}' en Google!"
1361
  new_url = f"https://www.google.com.ar/search?q={query.replace(' ', '+')}"
1362
+ chat_history.append({"role": "MateAI", "content": action_desc})
1363
+ action_output, browser_url, browser_content = simulate_browser_action(action_desc, new_url)
1364
+ return chat_history, "", conversation_context_state, action_output, browser_url, browser_content
1365
  else:
1366
  response = "¡Decime qué querés buscar, che! Por ejemplo: 'buscar el clima en Google'."
1367
+ chat_history.append({"role": "MateAI", "content": response})
1368
+ return chat_history, "", conversation_context_state, response, gr.skip(), gr.skip(), gr.skip()
1369
 
1370
  if "cerrar esta pagina" in user_message_lower or "cerrar pagina" in user_message_lower:
1371
  action_desc = "¡Listo, cerrando la página actual en el navegador simulado!"
1372
+ new_url = "about:blank" # Simulate a blank or closed page
1373
+ chat_history.append({"role": "MateAI", "content": action_desc})
1374
+ action_output, browser_url, browser_content = simulate_browser_action(action_desc, new_url)
1375
+ return chat_history, "", conversation_context_state, action_output, browser_url, browser_content
1376
 
1377
+ # Logic to request a contextual nudge
1378
  current_step = conversation_context_state["step"]
1379
  location = conversation_context_state["location"]
1380
  activity = conversation_context_state["activity"]
 
1394
  response = f"Entendido, en {location} y {activity}. ¿Y cómo te sentís? ¿Bien, cansado, bajoneado, motivado?"
1395
  conversation_context_state["step"] = "ask_sentiment"
1396
  else:
1397
+ # We have everything, generate the nudge
1398
  nudge_text, current_points, current_insignia, next_insignia_goal, historial = await nudge_generator.generate_nudge(user_obj.user_id, location, activity, sentiment)
1399
  response = nudge_text
1400
+ chat_history.append({"role": "MateAI", "content": response})
1401
+ # Reset context after generating the nudge
1402
  conversation_context_state = {"step": "initial", "pending_nudge_request": False, "location": None, "activity": None, "sentiment": None}
1403
+ return chat_history, "", conversation_context_state, response, gr.skip(), gr.skip(), gr.skip()
1404
 
1405
  elif current_step == "ask_location":
1406
  if any(k in user_message_lower for k in ["casa", "hogar"]):
 
1410
  elif any(k in user_message_lower for k in ["calle", "aire libre", "plaza", "parque", "caminando"]):
1411
  location = "Aire Libre/Calle"
1412
  else:
1413
+ response = "No te entendí bien, che. ¿Estás en casa, en la oficina o en la calle?"
1414
+ chat_history.append({"role": "MateAI", "content": response})
1415
+ return chat_history, "", conversation_context_state, response, gr.skip(), gr.skip(), gr.skip()
1416
 
1417
  conversation_context_state["location"] = location
1418
  if not activity:
 
1424
  else: # Should not happen if flow is sequential, but as fallback
1425
  nudge_text, current_points, current_insignia, next_insignia_goal, historial = await nudge_generator.generate_nudge(user_obj.user_id, location, activity, sentiment)
1426
  response = nudge_text
1427
+ chat_history.append({"role": "MateAI", "content": response})
1428
  conversation_context_state = {"step": "initial", "pending_nudge_request": False, "location": None, "activity": None, "sentiment": None}
1429
+ return chat_history, "", conversation_context_state, response, gr.skip(), gr.skip(), gr.skip()
1430
 
1431
  elif current_step == "ask_activity":
1432
  if any(k in user_message_lower for k in ["relajado", "tranqui", "descansando"]):
 
1442
  elif any(k in user_message_lower for k in ["partido", "fútbol", "viendo tele"]):
1443
  activity = "Viendo un partido"
1444
  else:
1445
+ response = "No te pesqué esa, che. ¿Estás laburando, haciendo ejercicio, relajado, cocinando, o viendo un partido?"
1446
+ chat_history.append({"role": "MateAI", "content": response})
1447
+ return chat_history, "", conversation_context_state, response, gr.skip(), gr.skip(), gr.skip()
1448
 
1449
  conversation_context_state["activity"] = activity
1450
  if not sentiment:
 
1453
  else: # Should not happen if flow is sequential, but as fallback
1454
  nudge_text, current_points, current_insignia, next_insignia_goal, historial = await nudge_generator.generate_nudge(user_obj.user_id, location, activity, sentiment)
1455
  response = nudge_text
1456
+ chat_history.append({"role": "MateAI", "content": response})
1457
  conversation_context_state = {"step": "initial", "pending_nudge_request": False, "location": None, "activity": None, "sentiment": None}
1458
+ return chat_history, "", conversation_context_state, response, gr.skip(), gr.skip(), gr.skip()
1459
 
1460
  elif current_step == "ask_sentiment":
1461
  if any(k in user_message_lower for k in ["bien", "joya", "diez puntos", "contento"]):
 
1467
  elif any(k in user_message_lower for k in ["motivado", "con pilas", "enchufado"]):
1468
  sentiment = "Motivado"
1469
  else:
1470
+ response = "No te capto el sentimiento, che. ¿Estás bien, cansado, bajoneado o motivado?"
1471
+ chat_history.append({"role": "MateAI", "content": response})
1472
+ return chat_history, "", conversation_context_state, response, gr.skip(), gr.skip(), gr.skip()
1473
 
1474
  conversation_context_state["sentiment"] = sentiment
1475
+ # Now that we have everything, generate the nudge
1476
  nudge_text, current_points, current_insignia, next_insignia_goal, historial = await nudge_generator.generate_nudge(user_obj.user_id, location, activity, sentiment)
1477
  response = nudge_text
1478
+ chat_history.append({"role": "MateAI", "content": response})
1479
+ # Reset context after generating the nudge
1480
  conversation_context_state = {"step": "initial", "pending_nudge_request": False, "location": None, "activity": None, "sentiment": None}
1481
+ return chat_history, "", conversation_context_state, response, gr.skip(), gr.skip(), gr.skip()
1482
 
1483
  else: # Default response if no specific command or context step
1484
  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."
1485
+ chat_history.append({"role": "MateAI", "content": response})
1486
+ return chat_history, "", conversation_context_state, response, gr.skip(), gr.skip(), gr.skip()
1487
 
1488
  # --- Conexión de Eventos ---
1489
  btn_crear_usuario.click(
 
1555
  outputs=[chatbot, voice_input_textbox, conversation_context_state, voice_output_text, simulated_action_output, simulated_browser_url, simulated_browser_content]
1556
  )
1557
 
1558
+ # When the backend generates text for voice, it sends it to voice_output_text,
1559
+ # and JS vocalizes it.
1560
  voice_output_text.change(
1561
+ fn=None, # No Python function, only used to activate JS
1562
  inputs=[voice_output_text],
1563
  outputs=None,
1564
  js="text => window.speakText(text)"
1565
  )
1566
 
1567
  # --- Conexión del botón oculto de cambio de vista ---
 
 
 
 
 
 
1568
  hidden_view_changer_button.click(
1569
  fn=change_view,
1570
  inputs=[hidden_view_input],
1571
+ outputs=[
1572
+ home_section, oracle_section, nudges_section,
1573
+ challenges_section, history_section, tasks_section,
1574
+ browser_agent_section, current_view_state
1575
+ ]
1576
  )
1577
 
1578
  # Lanza la interfaz de Gradio