import gradio as gr import os import re import json import requests from pypdf import PdfReader # ✅ Configurar API Key de OpenRouter desde variables de entorno OPENROUTER_API_KEY = os.getenv("sk-OsMMq65tXdfOIlTUYtocSL7NCsmA7CerN77OkEv29dODg1EA") if not OPENROUTER_API_KEY: raise ValueError("❌ OPENROUTER_API_KEY no está configurada. Ve a Settings > Variables de entorno en tu Space.") OPENROUTER_API_URL = "https://openrouter.ai/api/v1/chat/completions" # ✅ Modelo gratuito y potente: DeepSeek R1 MODEL = "deepseek/deepseek-r1-0528-qwen3-8b:free" def extract_text_from_pdf(pdf_file): """Extrae texto de un archivo PDF""" try: reader = PdfReader(pdf_file) text = "" for page in reader.pages: extracted = page.extract_text() if extracted: text += extracted + "\n" return text[:5000] # Limitar para no exceder tokens except Exception as e: return f"Error al leer PDF: {str(e)}" def generate_smart_objective(objective, age, duration): """Genera un objetivo SMART completo y detallado""" age_group = 'preescolar' if age < 36 else 'escolar' if age < 144 else 'adolescente/adulto' time_frame = 'corto plazo' if duration < 30 else 'mediano plazo' if duration < 60 else 'largo plazo' return f"El paciente {age_group} logrará {objective} con un 80% de precisión durante {duration} minutos, utilizando apoyo visual/auditivo según necesidad, medible a través de registro de respuestas correctas en {time_frame}." def call_openrouter_api(prompt): """Llama a la API de OpenRouter""" headers = { "Authorization": f"Bearer {OPENROUTER_API_KEY}", "Content-Type": "application/json", "HTTP-Referer": "https://tufonoayuda-pixel.github.io/ActFonoGenerator/", # Tu URL "X-Title": "Generador IA de Actividades Fonoaudiológicas" } data = { "model": MODEL, "messages": [{"role": "user", "content": prompt}], "temperature": 0.3, "max_tokens": 4096 } try: response = requests.post(OPENROUTER_API_URL, headers=headers, json=data, timeout=60) response.raise_for_status() result = response.json() return result["choices"][0]["message"]["content"] except Exception as e: raise Exception(f"Error al llamar a OpenRouter: {str(e)}") def generate_activity(user_desc, objective, duration, session_type, is_pediatric, context, pdf_files): """Genera una actividad terapéutica COMPLETA y DETALLADA con IA real de OpenRouter""" if not all([user_desc, objective, duration]): return ("⚠️ Error", "Por favor completa todos los campos obligatorios.", "", "", "", "", "", "") try: # Extraer edad age = int(re.search(r'\d+', user_desc).group()) if re.search(r'\d+', user_desc) else 60 is_child = age < 144 or is_pediatric dur = int(duration) # Procesar PDFs pdf_text = "" if pdf_files: for pdf_file in pdf_files: pdf_text += f"\n--- Contenido de {pdf_file.name} ---\n" pdf_text += extract_text_from_pdf(pdf_file) # ✅ Construir prompt detallado para OpenRouter prompt = f""" Eres un fonoaudiólogo experto con 20 años de experiencia clínica. Tu tarea es generar una ACTIVIDAD TERAPÉUTICA COMPLETA, DETALLADA Y LISTA PARA IMPLEMENTAR, basada en evidencia científica y buenas prácticas clínicas. PACIENTE: {user_desc} OBJETIVO TERAPÉUTICO: {objective} DURACIÓN DE LA SESIÓN: {duration} minutos TIPO DE SESIÓN: {session_type} ¿SESIÓN PEDIÁTRICA?: {'Sí, usar lenguaje lúdico y adaptado' if is_child else 'No, lenguaje profesional y técnico'} CONTEXTO ADICIONAL: {context or 'Ninguno'} {f'REFERENCIAS CIENTÍFICAS (usa esta información para fundamentar): {pdf_text}' if pdf_text else ''} INSTRUCCIONES ESPECÍFICAS PARA LA ACTIVIDAD: 1. TÍTULO: Crea un título atractivo, profesional y descriptivo que refleje el contenido de la actividad. 2. OBJETIVO SMART: Formula un objetivo terapéutico específico, medible, alcanzable, relevante y con tiempo definido. Debe ser una oración completa y detallada. 3. DESCRIPCIÓN GENERAL: Escribe un párrafo completo (mínimo 5-7 oraciones) que describa la actividad, su propósito, población objetivo, y cómo se relaciona con el objetivo terapéutico. 4. MATERIALES NECESARIOS: Lista todos los materiales requeridos con descripciones específicas y detalladas. No uses viñetas sueltas, escribe oraciones completas. 5. PROCEDIMIENTO PASO A PASO: Describe detalladamente las tres fases de la actividad (calentamiento, desarrollo, cierre). Para cada fase: - Nombre de la fase - Duración estimada - Instrucciones verbales exactas que debe dar el terapeuta - Actividades específicas que realizará el paciente - Posibles variaciones o adaptaciones durante la actividad - Señales de progreso o dificultad a observar 6. EVALUACIÓN: Define: - Criterios de logro específicos y medibles - Métodos de evaluación cuantitativos y cualitativos - Cómo se registrará el progreso - Cómo se dará retroalimentación al paciente/familia 7. ADAPTACIONES: Sugiere adaptaciones específicas para: - Diferentes niveles de habilidad - Contextos (clínico, domiciliario, educativo) - Características individuales del paciente - Disponibilidad de recursos 8. FUNDAMENTACIÓN TEÓRICA: Explica brevemente (2-3 párrafos completos) en qué teorías, modelos o enfoques se basa la actividad. Cita autores o referencias cuando sea posible. FORMATO DE RESPUESTA OBLIGATORIO (JSON con estas claves): {{ "title": "string (título completo y descriptivo)", "smart_objective": "string (oración completa y detallada)", "description": "string (párrafo completo de 5-7 oraciones mínimo)", "materials": "string (párrafo detallado describiendo todos los materiales)", "procedure": [ {{ "name": "string (nombre de la fase)", "time": number (duración en minutos), "instructions": "string (instrucciones verbales exactas del terapeuta)", "activities": "string (descripción detallada de las actividades del paciente)", "variations": "string (posibles variaciones o adaptaciones)", "progress_indicators": "string (señales de progreso o dificultad a observar)" }} ], "evaluation": {{ "criteria": "string (criterios de logro específicos y medibles)", "methods": "string (métodos de evaluación cuantitativos y cualitativos)", "recording": "string (cómo se registrará el progreso)", "feedback": "string (cómo se dará retroalimentación al paciente/familia)" }}, "adaptations": "string (párrafo completo describiendo todas las adaptaciones necesarias)", "theoretical_foundation": "string (2-3 párrafos completos con fundamentación teórica)" }} IMPORTANTE: TODAS las respuestas deben ser TEXTOS COMPLETOS, NO viñetas sueltas ni palabras aisladas. Usa lenguaje profesional, claro y detallado. La actividad debe ser práctica, realista y lista para implementar en una sesión clínica real. """ # ✅ Llamar a OpenRouter API content = call_openrouter_api(prompt) # Limpiar y parsear JSON if content.startswith("```json"): content = content[7:] if content.endswith("```"): content = content[:-3] data = json.loads(content) # ✅ Retornar resultados completos return ( data.get("title", f"Actividad Terapéutica para {objective}"), data.get("smart_objective", generate_smart_objective(objective, age, dur)), data.get("description", "Descripción detallada generada por IA no disponible."), data.get("materials", "Lista de materiales no disponible."), "\n\n".join([ f"FASE: {p.get('name', 'Fase no especificada')} ({p.get('time', 0)} minutos)\n" f"INSTRUCCIONES DEL TERAPEUTA: {p.get('instructions', 'No especificadas')}\n" f"ACTIVIDADES DEL PACIENTE: {p.get('activities', 'No especificadas')}\n" f"VARIACIONES: {p.get('variations', 'No especificadas')}\n" f"INDICADORES DE PROGRESO: {p.get('progress_indicators', 'No especificados')}" for p in data.get("procedure", []) ]), f"CRITERIOS DE LOGRO: {data.get('evaluation', {}).get('criteria', 'No especificados')}\n\n" f"MÉTODOS DE EVALUACIÓN: {data.get('evaluation', {}).get('methods', 'No especificados')}\n\n" f"REGISTRO DE PROGRESO: {data.get('evaluation', {}).get('recording', 'No especificado')}\n\n" f"RETROALIMENTACIÓN: {data.get('evaluation', {}).get('feedback', 'No especificada')}", data.get("adaptations", "Adaptaciones no disponibles."), data.get("theoretical_foundation", "Fundamentación teórica no disponible.") ) except Exception as e: # ✅ Mostrar error real return ( "❌ Error al generar con IA", f"Error: {str(e)}", "La IA no pudo generar una respuesta completa. Esto puede deberse a:\n" "• El prompt es demasiado largo o complejo\n" "• Problemas temporales con la API de OpenRouter\n" "• Tu API Key no es válida o ha excedido límites\n\n" "Sugerencias:\n" "• Intenta con una descripción más concisa\n" "• Reduce la cantidad de PDFs o contexto adicional\n" "• Intenta nuevamente en unos minutos", "", "", "", "", "" ) # ✅ Interfaz de Gradio with gr.Blocks(title="🧠 Generador IA de Actividades Fonoaudiológicas") as demo: gr.Markdown("# 🧠 Generador IA de Actividades Fonoaudiológicas") gr.Markdown("### ✨ Potenciado con DeepSeek R1 en OpenRouter • Creado por Flgo. Cristóbal San Martín [@tufonoayuda](https://instagram.com/tufonoayuda)") with gr.Row(): with gr.Column(): user_desc = gr.Textbox(label="👤 Descripción del usuario (edad en meses y contexto)", placeholder="Ej: Niño de 48 meses con dislalia funcional, buen nivel cognitivo pero con dificultades en la articulación de fonemas líquidos", lines=3) objective = gr.Textbox(label="🎯 Objetivo específico", placeholder="Ej: Mejorar la articulación del fonema /r/ en posición inicial de palabras bisílabas en contexto estructurado con 80% de precisión", lines=2) duration = gr.Number(label="⏱️ Duración (minutos)", value=30, minimum=15, maximum=120) session_type = gr.Dropdown(["individual", "grupal", "hogar"], label="👥 Tipo de sesión", value="individual") is_pediatric = gr.Checkbox(label="🧸 Sesión Pediátrica (lenguaje lúdico)") context = gr.Textbox(label="📚 Contexto Adicional (Opcional - Sé lo más específico posible)", placeholder="Ej: El niño responde bien a refuerzos visuales, tiene interés por los dinosaurios, la familia puede reforzar en casa 10 minutos diarios, se ha intentado terapia miofuncional sin éxito...", lines=4) pdf_files = gr.File(label="📖 Subir PDFs de Referencia (Opcional)", file_types=[".pdf"], file_count="multiple") btn = gr.Button("✨ Generar Actividad con IA", variant="primary") with gr.Column(): title = gr.Textbox(label="Título de la Actividad", lines=2) smart_obj = gr.Textbox(label="📋 Objetivo SMART", lines=3) description = gr.Textbox(label="📝 Descripción General", lines=6) materials = gr.Textbox(label="🎯 Materiales Necesarios", lines=4) procedure = gr.Textbox(label="⚡ Procedimiento Paso a Paso (Instrucciones completas)", lines=10) evaluation = gr.Textbox(label="📊 Evaluación (Criterios, métodos, registro y retroalimentación)", lines=6) adaptations = gr.Textbox(label="🔧 Adaptaciones (Párrafo completo)", lines=4) theory = gr.Textbox(label="📚 Fundamentación Teórica (2-3 párrafos completos)", lines=6) # Conectar botón btn.click( fn=generate_activity, inputs=[user_desc, objective, duration, session_type, is_pediatric, context, pdf_files], outputs=[title, smart_obj, description, materials, procedure, evaluation, adaptations, theory] ) # Lanzar app if __name__ == "__main__": demo.launch()