import time import os import joblib import streamlit as st import google.generativeai as genai from dotenv import load_dotenv # Función para cargar CSS personalizado def load_css(file_path): with open(file_path) as f: st.markdown(f'', unsafe_allow_html=True) # Intentar cargar el CSS personalizado con ruta absoluta para mayor seguridad try: css_path = os.path.join(os.path.dirname(__file__), 'static', 'css', 'style.css') load_css(css_path) except Exception as e: print(f"Error al cargar CSS: {e}") # Si el archivo no existe, crear un estilo básico en línea st.markdown(""" """, unsafe_allow_html=True) load_dotenv() GOOGLE_API_KEY=os.environ.get('GOOGLE_API_KEY') genai.configure(api_key=GOOGLE_API_KEY) new_chat_id = f'{time.time()}' MODEL_ROLE = 'ai' AI_AVATAR_ICON = '🤖' # Cambia el emoji por uno de robot para coincidir con tu logo USER_AVATAR_ICON = '👤' # Añade un avatar para el usuario # Definición del prompt multipersona para el sistema # Definición del prompt multipersona para el sistema SYSTEM_PROMPT = """ Eres un equipo colaborativo de expertos de clase mundial trabajando juntos para crear Propuestas Únicas de Valor (PUVs) excepcionales que conviertan a la audiencia en clientes. EL EQUIPO DE EXPERTOS: 1. ESTRATEGA MAESTRO DE MARKETING: - Experto en marcos de propuestas de valor y estrategias de conversión - Asegura que las PUVs sigan la estructura de fórmula seleccionada con precisión - Se enfoca en la colocación estratégica de elementos clave de conversión 2. COPYWRITER ELITE DE RESPUESTA DIRECTA: - Crea ganchos, historias y elementos persuasivos convincentes - Elabora propuestas de valor irresistibles que impulsan conversiones - Asegura que el lenguaje resuene con la audiencia objetivo 3. ESPECIALISTA EN PSICOLOGÍA DE AUDIENCIA: - Experto en comprender las motivaciones y objeciones de la audiencia - Crea contenido que construye conexión genuina y confianza - Identifica y aborda miedos y deseos ocultos 4. MAESTRO DE DIFERENCIACIÓN: - Crea propuestas únicas que destacan entre la competencia - Desarrolla ejemplos y casos de estudio relacionables - Asegura que las PUVs apoyen la transformación que se ofrece 5. EXPERTO EN CONVERSIÓN: - Se especializa en crear PUVs que mantengan la atención y generen acción - Crea elementos interactivos y ganchos de engagement - Asegura que las PUVs fluyan naturalmente y mantengan el interés alto IMPORTANTE: Cuando el usuario pregunte por tus funciones o capacidades, responde siempre en primera persona singular (yo hago, yo creo, yo analizo) de forma breve y concisa. No menciones que eres un equipo de expertos, sino un asistente especializado en crear PUVs. Si el usuario no proporciona información suficiente sobre su producto/servicio o audiencia objetivo, solicítala de manera amable y directa. """ # Mensaje de bienvenida mejorado WELCOME_MESSAGE = """ # 🚀 ¡Bienvenido! Soy RoboCopy, tu creador de PUVs 🚀 Soy un experto en crear **Propuestas Únicas de Valor (PUVs)** que transforman visitantes en clientes. ## 💼 ¿Qué puedo hacer por ti? Creo PUVs persuasivas que: - **Capturan la atención** de tu audiencia ideal - **Comunican claramente** el valor de tu oferta - **Convierten visitantes** en clientes leales ## 📋 Para ayudarte, necesito conocer: 1️⃣ **Tu producto o servicio**: ¿Qué ofreces exactamente? 2️⃣ **Tu audiencia objetivo**: ¿Quiénes son tus clientes ideales? Analizaré tu audiencia, identificaré los beneficios clave y crearé TRES propuestas de valor potentes siguiendo estas fórmulas: - **Problema-Solución**: Para tu audiencia con su problema, tu producto proporciona la solución - **Antes-Después**: Transforma la situación actual en la situación deseada - **Beneficio Principal**: Tu producto ayuda a conseguir el beneficio sin el obstáculo común **¡Comencemos!** Comparte los detalles de tu producto/servicio y audiencia objetivo. """ # Función para mostrar texto con efecto de escritura def mostrar_con_efecto_escritura(mensaje, velocidad=0.1): with st.chat_message(name=MODEL_ROLE, avatar=AI_AVATAR_ICON): message_placeholder = st.empty() full_response = '' # Indicador de escritura typing_indicator = st.empty() typing_indicator.markdown("*Estoy analizando tu información...*") # Mostrar respuesta por fragmentos for palabra in mensaje.split(' '): full_response += palabra + ' ' time.sleep(velocidad) message_placeholder.write(full_response + '▌') # Eliminar indicador y mostrar respuesta completa typing_indicator.empty() message_placeholder.write(mensaje) return mensaje # Create a data/ folder if it doesn't already exist try: os.mkdir('data/') except: # data/ folder already exists pass # Load past chats (if available) try: past_chats: dict = joblib.load('data/past_chats_list') except: past_chats = {} # Sidebar allows a list of past chats with st.sidebar: # Centrar el logo y eliminar el título de RoboCopy col1, col2, col3 = st.columns([1, 2, 1]) with col2: st.image("assets/robocopy_logo.png", width=300) st.write('# Chats Anteriores') if st.session_state.get('chat_id') is None: st.session_state.chat_id = st.selectbox( label='Selecciona un chat anterior', options=[new_chat_id] + list(past_chats.keys()), format_func=lambda x: past_chats.get(x, 'Nuevo Chat'), placeholder='_', ) else: # This will happen the first time AI response comes in st.session_state.chat_id = st.selectbox( label='Selecciona un chat anterior', options=[new_chat_id, st.session_state.chat_id] + list(past_chats.keys()), index=1, format_func=lambda x: past_chats.get(x, 'Nuevo Chat' if x != st.session_state.chat_id else st.session_state.chat_title), placeholder='_', ) # Inicializar el título de chat si es necesario if st.session_state.get('chat_title') is None: if st.session_state.chat_id in past_chats: st.session_state.chat_title = past_chats[st.session_state.chat_id] else: st.session_state.chat_title = 'Nuevo Chat' st.write('# Creador de Propuestas Únicas de Valor') # Inicializar mensajes y cargar historial if not st.session_state.get('messages'): st.session_state.messages = [] # Cargar historial de chat si existe try: st.session_state.messages = joblib.load(f'data/{st.session_state.chat_id}-st_messages') st.session_state.gemini_history = joblib.load(f'data/{st.session_state.chat_id}-gemini_messages') print('old cache') except: if not st.session_state.get('messages'): st.session_state.messages = [] st.session_state.gemini_history = [] print('new_cache made') # Configuración del modelo model = genai.GenerativeModel(model_name='gemini-2.0-flash') st.session_state.model = model st.session_state.chat = st.session_state.model.start_chat(history=st.session_state.gemini_history) # Si es un chat nuevo, enviar el prompt del sistema como primer mensaje if not st.session_state.gemini_history: # Enviamos el prompt del sistema como primer mensaje (invisible para el usuario) st.session_state.chat.send_message(SYSTEM_PROMPT) st.session_state.gemini_history = st.session_state.chat.history # Mostrar mensajes del historial for message in st.session_state.messages: with st.chat_message(name=message['role'], avatar=message.get('avatar')): st.markdown(message['content']) # React to user input if prompt := st.chat_input('¿En qué puedo ayudarte hoy?'): # Verificar si es el primer mensaje del usuario is_first_message = len(st.session_state.messages) == 0 # Guardar información del chat if st.session_state.chat_id not in past_chats.keys(): # Es una nueva conversación, generamos un título basado en el primer mensaje temp_title = f'SesiónChat-{st.session_state.chat_id}' past_chats[st.session_state.chat_id] = temp_title # Generamos un título basado en el contenido del mensaje try: title_generator = genai.GenerativeModel('gemini-2.0-flash') title_response = title_generator.generate_content( f"Genera un título corto (máximo 5 palabras) que describa de qué trata esta consulta, sin usar comillas ni puntuación: '{prompt}'") generated_title = title_response.text.strip() if generated_title: st.session_state.chat_title = generated_title past_chats[st.session_state.chat_id] = generated_title else: st.session_state.chat_title = temp_title except Exception as e: print(f"Error al generar título: {e}") st.session_state.chat_title = temp_title else: # Ya existe esta conversación, usamos el título guardado st.session_state.chat_title = past_chats[st.session_state.chat_id] joblib.dump(past_chats, 'data/past_chats_list') # Mostrar mensaje del usuario with st.chat_message('user', avatar=USER_AVATAR_ICON): st.markdown(prompt) # Añadir mensaje del usuario al historial st.session_state.messages.append({ 'role': 'user', 'content': prompt, }) # Si es el primer mensaje, mostrar el mensaje de bienvenida if is_first_message: with st.chat_message(name=MODEL_ROLE, avatar=AI_AVATAR_ICON): st.markdown(WELCOME_MESSAGE) st.session_state.messages.append({ 'role': MODEL_ROLE, 'content': WELCOME_MESSAGE, 'avatar': AI_AVATAR_ICON, }) # Guardar el historial actualizado joblib.dump(st.session_state.messages, f'data/{st.session_state.chat_id}-st_messages') # Salir de la función para no procesar más este mensaje st.rerun() # Implementación de reintentos con retroceso exponencial max_retries = 3 retry_count = 0 while retry_count < max_retries: try: # Enviar mensaje al modelo response = st.session_state.chat.send_message(prompt, stream=True) # Procesar la respuesta completa full_text = "" for chunk in response: full_text += chunk.text # Mostrar respuesta del asistente con efecto de escritura mensaje_mostrado = mostrar_con_efecto_escritura(full_text) # Añadir respuesta al historial st.session_state.messages.append({ 'role': MODEL_ROLE, 'content': mensaje_mostrado, 'avatar': AI_AVATAR_ICON, }) # Actualizar historial st.session_state.gemini_history = st.session_state.chat.history # Guardar historial joblib.dump(st.session_state.messages, f'data/{st.session_state.chat_id}-st_messages') joblib.dump(st.session_state.gemini_history, f'data/{st.session_state.chat_id}-gemini_messages') # Salir del bucle si la solicitud fue exitosa break except Exception as e: retry_count += 1 if retry_count >= max_retries: # Mostrar mensaje de error si se agotan los reintentos with st.chat_message(name=MODEL_ROLE, avatar=AI_AVATAR_ICON): st.error(f"Lo siento, estoy experimentando problemas para procesar tu solicitud. Por favor, intenta de nuevo más tarde. Error: {str(e)}") st.session_state.messages.append({ 'role': MODEL_ROLE, 'content': f"Lo siento, estoy experimentando problemas para procesar tu solicitud. Por favor, intenta de nuevo más tarde. Error: {str(e)}", 'avatar': AI_AVATAR_ICON, }) joblib.dump(st.session_state.messages, f'data/{st.session_state.chat_id}-st_messages') else: # Esperar antes de reintentar (retroceso exponencial) wait_time = (2 ** retry_count) + (time.time() % 1) time.sleep(wait_time) # Salir del bucle si la solicitud fue exitosa break