Basicomanchas / app.py
JairoCesar's picture
Update app.py
a83f5b8 verified
# ==================== 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 ---