Update app.py
Browse files
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 |
-
|
| 38 |
-
|
| 39 |
-
|
| 40 |
-
cred = credentials.Certificate(
|
| 41 |
firebase_admin.initialize_app(cred)
|
| 42 |
-
print("Firebase Admin SDK inicializado correctamente desde variable de entorno.")
|
| 43 |
else:
|
| 44 |
-
#
|
| 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
|
| 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 |
-
//
|
| 779 |
function startListening() {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 780 |
if (recognition) {
|
| 781 |
recognition.stop();
|
|
|
|
| 782 |
}
|
|
|
|
| 783 |
recognition = new (window.SpeechRecognition || window.webkitSpeechRecognition)();
|
| 784 |
-
recognition.lang = 'es-AR'; //
|
| 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; //
|
| 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) { //
|
| 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); //
|
| 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); //
|
|
|
|
| 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 |
-
//
|
| 840 |
function speakText(text) {
|
| 841 |
-
if (!text || text === lastSpokenText) return; //
|
| 842 |
lastSpokenText = text;
|
| 843 |
|
| 844 |
const utterance = new SpeechSynthesisUtterance(text);
|
| 845 |
-
utterance.lang = 'es-AR'; //
|
| 846 |
|
| 847 |
-
//
|
| 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); //
|
| 880 |
-
|
| 881 |
-
|
| 882 |
-
|
| 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); //
|
|
|
|
| 894 |
};
|
| 895 |
|
| 896 |
window.speechSynthesis.speak(utterance);
|
| 897 |
}
|
| 898 |
|
| 899 |
-
//
|
| 900 |
window.speakText = speakText;
|
| 901 |
|
| 902 |
-
//
|
| 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; //
|
| 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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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
|
| 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
|
| 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
|
| 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
|
| 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
|
| 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 |
-
#
|
| 1138 |
def change_view(view_name):
|
| 1139 |
-
|
| 1140 |
-
|
| 1141 |
-
"
|
| 1142 |
-
"
|
| 1143 |
-
"
|
| 1144 |
-
"
|
| 1145 |
-
"
|
| 1146 |
-
"
|
| 1147 |
-
|
| 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 |
-
#
|
| 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 |
-
#
|
| 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 #
|
| 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 #
|
| 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 |
-
|
| 1234 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
|
| 1241 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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(
|
| 1254 |
-
|
|
|
|
| 1255 |
|
| 1256 |
-
#
|
|
|
|
|
|
|
|
|
|
| 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(
|
| 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(
|
| 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(
|
| 1272 |
-
return chat_history, "", conversation_context_state, msg
|
| 1273 |
else:
|
| 1274 |
-
|
| 1275 |
-
|
|
|
|
| 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(
|
| 1282 |
-
return chat_history, "", conversation_context_state, msg
|
| 1283 |
else:
|
| 1284 |
-
|
| 1285 |
-
|
|
|
|
| 1286 |
|
| 1287 |
-
# ---
|
| 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(
|
| 1293 |
-
|
|
|
|
| 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(
|
| 1298 |
-
|
|
|
|
| 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" #
|
| 1302 |
-
chat_history.append(
|
| 1303 |
-
|
|
|
|
| 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(
|
| 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(
|
| 1315 |
-
|
|
|
|
| 1316 |
else:
|
| 1317 |
response = "¡Decime qué querés buscar, che! Por ejemplo: 'buscar el clima en Google'."
|
| 1318 |
-
chat_history.append(
|
| 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" #
|
| 1324 |
-
chat_history.append(
|
| 1325 |
-
|
|
|
|
| 1326 |
|
| 1327 |
-
#
|
| 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 |
-
#
|
| 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(
|
| 1351 |
-
#
|
| 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 |
-
|
| 1364 |
-
|
|
|
|
| 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(
|
| 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 |
-
|
| 1395 |
-
|
|
|
|
| 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(
|
| 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 |
-
|
| 1419 |
-
|
|
|
|
| 1420 |
|
| 1421 |
conversation_context_state["sentiment"] = sentiment
|
| 1422 |
-
#
|
| 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(
|
| 1426 |
-
#
|
| 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(
|
| 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 |
-
#
|
| 1506 |
-
#
|
| 1507 |
voice_output_text.change(
|
| 1508 |
-
fn=None, # No
|
| 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=[
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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
|