# ==================== 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() @st.cache_resource 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 ---