Update app.py
Browse files
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
|
| 14 |
-
#
|
| 15 |
-
#
|
| 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
|
| 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 |
-
#
|
| 32 |
-
#
|
| 33 |
-
#
|
| 34 |
-
#
|
| 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 |
-
#
|
| 44 |
try:
|
| 45 |
-
firebase_admin.
|
| 46 |
-
|
| 47 |
-
|
| 48 |
-
|
| 49 |
-
|
| 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 |
-
|
| 56 |
-
|
| 57 |
-
|
| 58 |
-
|
| 59 |
-
|
| 60 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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:
|
| 528 |
-
return "Error: Por favor, crea o carga un usuario primero.", ""
|
| 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:
|
| 541 |
-
return "Error: Por favor, crea o carga un usuario primero.", ""
|
| 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
|
| 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
|
| 806 |
const voices = window.speechSynthesis.getVoices();
|
| 807 |
-
|
| 808 |
-
|
| 809 |
-
|
| 810 |
-
|
| 811 |
-
|
| 812 |
-
|
|
|
|
|
|
|
| 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
|
| 875 |
gr.Markdown("<h2 style='text-align: center; color: #38bdf8; margin-top: 2em;'>Navegación MateAI</h2>")
|
| 876 |
-
|
| 877 |
-
|
| 878 |
-
|
| 879 |
-
|
| 880 |
-
|
| 881 |
-
|
| 882 |
-
|
| 883 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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="
|
| 992 |
gr.Markdown(
|
| 993 |
"""
|
| 994 |
-
<h2 style='text-align: center; color: #10b981;'
|
| 995 |
<p style='text-align: center;'>
|
| 996 |
-
Imaginemos el futuro: MateAI como un
|
| 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 |
-
|
| 1000 |
</p>
|
| 1001 |
"""
|
| 1002 |
)
|
| 1003 |
with gr.Row():
|
| 1004 |
with gr.Column():
|
| 1005 |
-
gr.Markdown("<h3 style='color: #38bdf8;'>
|
| 1006 |
-
|
| 1007 |
-
|
| 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 |
-
<
|
| 1017 |
-
<
|
| 1018 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1019 |
"""
|
| 1020 |
)
|
| 1021 |
-
|
| 1022 |
-
gr.Markdown("<h3 style='color: #38bdf8;'>
|
| 1023 |
-
gr.
|
| 1024 |
-
|
| 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 |
-
|
| 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
|
| 1041 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
"
|
| 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 |
-
|
| 1147 |
-
|
| 1148 |
-
|
| 1149 |
-
|
| 1150 |
-
|
| 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 |
-
|
| 1160 |
-
|
| 1161 |
-
|
| 1162 |
-
|
| 1163 |
-
|
| 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
|
| 1440 |
all_sections = [
|
| 1441 |
"home_section", "oracle_section", "nudges_section",
|
| 1442 |
"challenges_section", "history_section", "tasks_section",
|
| 1443 |
-
"
|
| 1444 |
]
|
| 1445 |
|
| 1446 |
-
|
| 1447 |
-
fn=
|
| 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;">● ● ●</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 |
|