Spaces:
Runtime error
Runtime error
| 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() |