Spaces:
Running
Running
| # ==================== Interprete de manchas de Test de Rorschach ===================================== | |
| # | |
| # (o\ | /o) | |
| # \.\_/. / | |
| # .--._/oo\_.--. | |
| # \ VVVV/ \VVVV / | |
| # \____/ \____/ | |
| # __________________________________________________________________________________________________________ | |
| # DIANA MILENA SOLER Psicologa Est Medicina U. Juan N Corpas JAIRO ALEXANDER ERASO MD U Nacional de Colombia | |
| # | |
| import streamlit as st | |
| import google.generativeai as genai # Import Gemini | |
| from docx import Document | |
| import tempfile | |
| import os | |
| import re | |
| import logging | |
| import datetime | |
| # Configurar logging para monitorear las llamadas al API | |
| logging.basicConfig( | |
| level=logging.INFO, | |
| format='%(asctime)s - %(levelname)s - %(message)s', | |
| handlers=[ | |
| logging.StreamHandler() | |
| ] | |
| ) | |
| logger = logging.getLogger("rorschach_gemini_app") | |
| # Configuración mínima de Streamlit | |
| st.set_page_config( | |
| page_title="Interpretación Rorschach con Gemini", | |
| page_icon="🧠", | |
| ) | |
| # --- CONFIGURACIÓN Gemini --- | |
| GEMINI_API_KEY = os.getenv("GEMINI_API_KEY") | |
| if not GEMINI_API_KEY: | |
| st.error("GEMINI_API_KEY no encontrada. Por favor configúrala en tus variables de entorno.") | |
| logger.error("GEMINI_API_KEY no encontrada.") | |
| st.stop() | |
| try: | |
| genai.configure(api_key=GEMINI_API_KEY) | |
| logger.info("✅ Configuración de Gemini API realizada.") | |
| except Exception as e: | |
| error_msg = f"❌ Error al configurar Gemini API: {str(e)}" | |
| logger.error(error_msg) | |
| st.error(error_msg) | |
| st.stop() | |
| def get_gemini_model(): | |
| logger.info("🔄 Cargando modelo Gemini...") | |
| # Puedes cambiar a "gemini-pro" si lo prefieres, gemini-1.5-flash es más rápido y económico | |
| return genai.GenerativeModel("gemini-2.5-flash-lite") | |
| # Inicializar modelo Gemini | |
| model = None | |
| try: | |
| model = get_gemini_model() | |
| logger.info("✅ Modelo Gemini cargado correctamente (gemini-2.5-flash-lite)") | |
| except Exception as e: | |
| error_msg = f"❌ Error al inicializar modelo Gemini: {str(e)}" | |
| logger.error(error_msg) | |
| st.error(error_msg) | |
| # st.stop() # Decidir si detener la app si el modelo no carga | |
| # Formatear prompt para interpretación Rorschach con Gemini | |
| def format_prompt_gemini(message): | |
| system_prompt = """Eres un experto en interpretación del Test de Rorschach. | |
| Analiza las respuestas del usuario a las láminas y proporciona una interpretación | |
| psicológica detallada basada en el método de Exner. | |
| Considera: | |
| - Localización de las respuestas | |
| - Determinantes (forma, color, movimiento) | |
| - Contenido de las respuestas | |
| - Originalidad/popularidad | |
| - Funcionamiento cognitivo, afectivo e interpersonal | |
| Al final describe una conclusion creativa en terminos sencillos de la personalidad del usuario | |
| con recomendaciones generales. | |
| """ | |
| # Gemini prefiere una concatenación más directa o el uso de 'parts' para roles. | |
| # Para una única llamada de generación de texto, concatenar es simple. | |
| prompt = f"{system_prompt}\n\nInterpretación Rorschach para las siguientes respuestas del usuario:\n{message}" | |
| logger.info(f"Prompt para Gemini generado con {len(prompt)} caracteres") | |
| return prompt | |
| # Generar respuesta desde Gemini | |
| def generate_with_gemini(user_input_message): | |
| if not model: | |
| logger.error("❌ No hay modelo Gemini disponible para generar respuesta") | |
| return "Error de conexión con el modelo Gemini. Por favor, inténtelo de nuevo más tarde." | |
| try: | |
| logger.info(f"🔄 Iniciando llamada al API de Gemini - {datetime.datetime.now()}") | |
| generation_config = genai.types.GenerationConfig( | |
| temperature=0.7, # Gemini usa un rango similar, 0.9 es bastante creativo | |
| max_output_tokens=1024, # Aumentado un poco para asegurar respuestas completas | |
| top_p=0.95, | |
| # top_k es otro parámetro que podrías usar en Gemini | |
| # repetition_penalty no es un parámetro directo en GenerationConfig | |
| # do_sample es implícito si temperature > 0 | |
| # seed no es un parámetro directo de GenerationConfig para `generate_content` | |
| ) | |
| formatted_prompt = format_prompt_gemini(user_input_message) | |
| start_time = datetime.datetime.now() | |
| # Llamada a Gemini | |
| response = model.generate_content( | |
| formatted_prompt, | |
| generation_config=generation_config | |
| ) | |
| end_time = datetime.datetime.now() | |
| duration = (end_time - start_time).total_seconds() | |
| logger.info(f"✅ Respuesta de Gemini generada en {duration:.2f} segundos") | |
| # Acceder al texto de la respuesta | |
| if response.parts: | |
| output_text = response.text | |
| else: | |
| # Esto podría ocurrir si la respuesta fue bloqueada por filtros de seguridad | |
| logger.warning(f"⚠️ Respuesta de Gemini vacía o bloqueada. Razón: {response.prompt_feedback}") | |
| output_text = ( | |
| "No se pudo generar una respuesta. Esto podría deberse a filtros de seguridad " | |
| f"o a un problema con la solicitud. Razón del feedback: {response.prompt_feedback}" | |
| ) | |
| return output_text | |
| except Exception as e: | |
| error_msg = f"❌ Error en la generación con Gemini: {str(e)}" | |
| logger.error(error_msg) | |
| # Verificar si el error es por API key inválida (aunque ya se verifica al inicio) | |
| if "API_KEY_INVALID" in str(e): | |
| st.error("La clave API de Gemini es inválida o ha expirado. Verifica tu configuración.") | |
| return f"Lo siento, ocurrió un error durante la interpretación con Gemini. Detalles: {str(e)}" | |
| # Reemplazo de variables en documento Word | |
| from docx.enum.text import WD_ALIGN_PARAGRAPH | |
| def replace_variables_word(doc, variables): | |
| for paragraph in doc.paragraphs: | |
| for key, value in variables.items(): | |
| if f'<{key}>' in paragraph.text: | |
| # Usar run para preservar formato si es posible, pero simple replace es más robusto para placeholder | |
| # Si el placeholder está solo, paragraph.text es suficiente | |
| # Si está entre otro texto, se necesita más cuidado con runs | |
| new_text = paragraph.text.replace(f'<{key}>', str(value)) # Asegurar que value sea string | |
| if paragraph.text != new_text: # Solo si hubo cambio | |
| paragraph.text = new_text | |
| paragraph.alignment = WD_ALIGN_PARAGRAPH.LEFT # Justificación | |
| for table in doc.tables: | |
| for row in table.rows: | |
| for cell in row.cells: | |
| for paragraph in cell.paragraphs: | |
| for key, value in variables.items(): | |
| if f'<{key}>' in paragraph.text: | |
| new_text = paragraph.text.replace(f'<{key}>', str(value)) | |
| if paragraph.text != new_text: | |
| paragraph.text = new_text | |
| paragraph.alignment = WD_ALIGN_PARAGRAPH.LEFT | |
| # Generar documento Word con interpretación | |
| def generate_word_document(interpretation): | |
| try: | |
| template_path = os.path.join('PLANTILLAS', 'PLANTILLA_INTERPRETACION.docx') | |
| if not os.path.exists(template_path): | |
| logger.warning(f"⚠️ No se encontró la plantilla en {template_path}") | |
| st.warning(f"No se encontró la plantilla en {template_path}") | |
| return None | |
| doc = Document(template_path) | |
| variables = {'INTERPRETACION': interpretation} | |
| replace_variables_word(doc, variables) | |
| with tempfile.NamedTemporaryFile(delete=False, suffix='.docx') as tmp: | |
| doc.save(tmp.name) | |
| logger.info(f"✅ Documento Word generado correctamente: {tmp.name}") | |
| return tmp.name | |
| except Exception as e: | |
| error_msg = f"❌ Error al generar el documento: {str(e)}" | |
| logger.error(error_msg) | |
| st.error(error_msg) | |
| return None | |
| # Interfaz Streamlit | |
| st.title("Interpretación del Test de Rorschach (con Gemini)") | |
| logger.info("🚀 Aplicación (Gemini) iniciada") | |
| entrada_usuario = st.text_area( | |
| "Ingrese respuestas del Test de Rorschach:", | |
| height=150, | |
| placeholder="Ejemplo: Lámina I: Veo un murciélago sobre un noche estrellada..." | |
| ) | |
| if st.button("Enviar Interpretación", type="primary"): | |
| if not entrada_usuario: | |
| st.warning("Por favor ingrese texto para interpretar") | |
| elif not model: # Verificar si el modelo se cargó | |
| st.error("El modelo de IA no está disponible. Por favor, revise la configuración y los logs.") | |
| logger.error("Intento de generar interpretación sin modelo cargado.") | |
| else: | |
| logger.info(f"🔄 Procesando entrada de {len(entrada_usuario)} caracteres para Gemini") | |
| with st.spinner("Generando interpretación con Gemini..."): | |
| bot_response = generate_with_gemini(entrada_usuario) # Llamada a la nueva función | |
| st.subheader("Interpretación (Gemini)") | |
| st.markdown(bot_response) # Usar markdown para mejor formato si Gemini lo usa | |
| if "No se pudo generar una respuesta" not in bot_response and "ocurrió un error" not in bot_response : | |
| document_path = generate_word_document(bot_response) | |
| if document_path: | |
| with open(document_path, "rb") as file: | |
| file_data = file.read() | |
| st.download_button( | |
| label="Descargar Interpretación", | |
| data=file_data, | |
| file_name="Interpretacion_Rorschach_Gemini.docx", | |
| mime="application/vnd.openxmlformats-officedocument.wordprocessingml.document" | |
| ) | |
| logger.info("📄 Botón de descarga (Gemini) mostrado al usuario") | |
| # Clean up the temporary file | |
| try: | |
| os.remove(document_path) | |
| logger.info(f"🧹 Archivo temporal {document_path} eliminado.") | |
| except OSError as e_rm: | |
| logger.error(f"⚠️ Error al eliminar archivo temporal {document_path}: {e_rm}") | |
| else: | |
| logger.error("❌ No se pudo generar el documento Word (Gemini)") | |
| else: | |
| logger.warning("⚠️ No se generó documento Word debido a error en la interpretación.") | |
| # --- END OF FILE manchas_gemini.py --- |