Spaces:
Running
Running
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'<style>{f.read()}</style>', 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(""" | |
<style> | |
.robocopy-title { | |
color: #4ECDC4 !important; | |
font-weight: bold; | |
font-size: 2em; | |
} | |
</style> | |
""", 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()}' | |
# Configuración de avatares e identificadores | |
MODEL_ROLE = 'ai' | |
AI_AVATAR_ICON = '🤖' | |
USER_AVATAR_ICON = '👤' | |
# Función para manejar títulos de chat de manera unificada | |
def get_chat_title(messages): | |
if not messages: | |
return "Nuevo Chat" | |
first_msg = messages[0]['content'] if messages else "" | |
# Limitar el título a los primeros 30 caracteres | |
title = first_msg[:30] + "..." if len(first_msg) > 30 else first_msg | |
return title | |
# Inicializar el estado de la sesión | |
if 'chats_in_memory' not in st.session_state: | |
st.session_state.chats_in_memory = {} | |
if 'current_chat_id' not in st.session_state: | |
st.session_state.current_chat_id = str(time.time()) | |
if 'messages' not in st.session_state: | |
st.session_state.messages = [] | |
# Importar el diccionario de fórmulas | |
from puv_formulas import puv_formulas | |
# Función para obtener descripciones de fórmulas para el prompt | |
def get_formulas_for_prompt(): | |
prompt_text = "FÓRMULAS DE PROPUESTAS ÚNICAS DE VALOR (PUVs):\n\n" | |
for key, formula in puv_formulas.items(): | |
prompt_text += f"FÓRMULA {key}:\n" | |
prompt_text += f"- Descripción: {formula['description']}\n" | |
prompt_text += "- Ejemplos:\n" | |
# Añadir algunos ejemplos (limitados para no hacer el prompt demasiado largo) | |
for i, example in enumerate(formula['examples'][:2]): | |
prompt_text += f" * Ejemplo {i+1}: {example['uvp']}\n" | |
prompt_text += "\n" | |
return prompt_text | |
# Definición del prompt multipersona para el sistema | |
SYSTEM_PROMPT = f""" | |
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 | |
{get_formulas_for_prompt()} | |
INSTRUCCIONES PARA CREAR PUVs: | |
1. Si el usuario no ha proporcionado información sobre su producto/servicio y audiencia objetivo, solicítala de manera amable y directa. | |
2. Si el usuario ha proporcionado información sobre su producto/servicio y audiencia objetivo, pero no ha elegido una fórmula específica, pregúntale qué fórmula le gustaría utilizar. Presenta las opciones disponibles: | |
- Fórmula Tradicional | |
- Fórmula Anti-tradicional | |
- Contrato Imposible | |
- Reto Ridículo | |
3. Una vez que el usuario haya proporcionado toda la información necesaria (producto/servicio, audiencia objetivo y fórmula elegida), procede a: | |
a. Analizar internamente (sin mostrar este análisis) la información del producto/servicio y la audiencia objetivo | |
b. Identificar internamente el problema principal y el beneficio clave | |
c. Crear propuestas de valor utilizando ÚNICAMENTE la fórmula elegida por el usuario | |
d. Presentar SOLO las PUVs, sin explicaciones adicionales sobre por qué son efectivas | |
ANÁLISIS INTERNO DEL PÚBLICO OBJETIVO (NO INCLUIR EN LA RESPUESTA): | |
Para cada solicitud, realiza un análisis interno de los siguientes puntos: | |
1. ANÁLISIS DEL PÚBLICO OBJETIVO - Punto de Dolor Principal: | |
- ¿Cuál es la frustración específica más importante de esta audiencia? | |
- ¿Cuál es su mayor desafío diario? | |
2. ANÁLISIS DEL PRODUCTO/SERVICIO - Beneficio Principal: | |
- ¿Cuál es el resultado tangible más importante que obtienen los clientes? | |
- ¿Cuál es la transformación específica más valiosa que ofrece? | |
INSTRUCCIONES CRÍTICAS PARA TODAS LAS FÓRMULAS: | |
- Cada PUV debe enfocarse en UN SOLO problema/dolor principal | |
- Cada PUV debe ofrecer UNA transformación clara y específica | |
- Usa lenguaje natural y conversacional | |
- Evita frases genéricas y palabras de moda | |
- Sé claro y conciso en cada PUV | |
- NO incluyas explicaciones sobre por qué la PUV es efectiva | |
- SOLO presenta la PUV, nada más | |
COMPORTAMIENTO COMO AGENTE: | |
- Al final de CADA respuesta, SIEMPRE pregunta al usuario qué más le gustaría hacer | |
- Ofrece opciones específicas como: mejorar las PUVs, refinarlas, cambiar de fórmula, o crear nuevas para otro producto/servicio | |
- Actúa como un agente proactivo que busca satisfacer al usuario | |
- Muestra entusiasmo por ayudar y ofrece sugerencias concretas para continuar | |
- Mantén un tono conversacional y amigable en todo momento | |
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. | |
""" | |
# 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? | |
3️⃣ **Fórmula de PUV**: ¿Qué tipo de fórmula prefieres? | |
4️⃣ **Número de ejemplos**: ¿Cuántos ejemplos te gustaría recibir? | |
### Fórmulas disponibles: | |
- **Fórmula Tradicional**: Comienza con "Yo ayudo a..." seguido de avatar y transformación | |
- **Fórmula Anti-tradicional**: Usa frases como "Yo transformo...", "Me especializo en..." | |
- **Contrato Imposible**: Usa frases como "¿Te imaginas poder...", "Soy el antídoto para..." | |
- **Reto Ridículo**: Usa anécdotas personales y humor para conectar con la audiencia | |
**¡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.05): | |
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("*RoboCopy está escribiendo...*") | |
# 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 = {} | |
# Inicializar el estado de sesión | |
if 'chats_in_memory' not in st.session_state: | |
st.session_state.chats_in_memory = {} | |
if 'current_chat_id' not in st.session_state: | |
st.session_state.current_chat_id = new_chat_id | |
if 'chat_title' not in st.session_state: | |
st.session_state.chat_title = 'Nuevo Chat' | |
# 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('current_chat_id') is None: | |
st.session_state.current_chat_id = st.selectbox( | |
label='Selecciona un chat anterior', | |
options=[new_chat_id] + list(st.session_state.chats_in_memory.keys()), | |
format_func=lambda x: st.session_state.chats_in_memory.get(x, {}).get('title', 'Nuevo Chat') if isinstance(x, str) else 'Nuevo Chat', | |
placeholder='_', | |
) | |
else: | |
st.session_state.current_chat_id = st.selectbox( | |
label='Selecciona un chat anterior', | |
options=[new_chat_id, st.session_state.current_chat_id] + list(st.session_state.chats_in_memory.keys()), | |
index=1, | |
format_func=lambda x: st.session_state.chats_in_memory.get(x, {}).get('title', 'Nuevo Chat') if isinstance(x, str) else 'Nuevo Chat', | |
placeholder='_', | |
) | |
# Botón para borrar el historial | |
if st.button('🗑️ Borrar Historial de Chat Actual'): | |
if st.session_state.current_chat_id in st.session_state.chats_in_memory: | |
del st.session_state.chats_in_memory[st.session_state.current_chat_id] | |
st.session_state.messages = [] | |
st.session_state.gemini_history = [] | |
st.session_state.chat_title = 'Nuevo Chat' | |
st.rerun() | |
# Inicializar mensajes y cargar historial | |
if not st.session_state.get('messages'): | |
st.session_state.messages = [] | |
# Cargar historial de chat si existe en memoria | |
if st.session_state.current_chat_id in st.session_state.chats_in_memory: | |
chat_data = st.session_state.chats_in_memory[st.session_state.current_chat_id] | |
st.session_state.messages = chat_data.get('messages', []) | |
st.session_state.gemini_history = chat_data.get('gemini_history', []) | |
st.session_state.chat_title = chat_data.get('title', 'Nuevo Chat') | |
else: | |
st.session_state.messages = [] | |
st.session_state.gemini_history = [] | |
st.session_state.chat_title = 'Nuevo Chat' | |
# 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.current_chat_id not in st.session_state.chats_in_memory: | |
# Es una nueva conversación, generamos un título basado en el primer mensaje | |
temp_title = f'SesiónChat-{st.session_state.current_chat_id}' | |
# 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 | |
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 | |
# Guardar en memoria | |
st.session_state.chats_in_memory[st.session_state.current_chat_id] = { | |
'messages': st.session_state.messages, | |
'gemini_history': st.session_state.gemini_history, | |
'title': st.session_state.chat_title | |
} | |
# 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, | |
'avatar': USER_AVATAR_ICON | |
}) | |
# 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, | |
}) | |
# Actualizar chat en memoria | |
st.session_state.chats_in_memory[st.session_state.current_chat_id].update({ | |
'messages': st.session_state.messages, | |
'gemini_history': st.session_state.gemini_history, | |
'title': st.session_state.chat_title | |
}) | |
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, velocidad=0.05) | |
# 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 | |
# Actualizar chat en memoria | |
st.session_state.chats_in_memory[st.session_state.current_chat_id].update({ | |
'messages': st.session_state.messages, | |
'gemini_history': st.session_state.gemini_history, | |
'title': st.session_state.chat_title | |
}) | |
break | |
except Exception as e: | |
retry_count += 1 | |
if retry_count >= max_retries: | |
error_message = f"Lo siento, estoy experimentando problemas para procesar tu solicitud. Por favor, intenta de nuevo más tarde. Error: {str(e)}" | |
with st.chat_message(name=MODEL_ROLE, avatar=AI_AVATAR_ICON): | |
st.error(error_message) | |
st.session_state.messages.append({ | |
'role': MODEL_ROLE, | |
'content': error_message, | |
'avatar': AI_AVATAR_ICON, | |
}) | |
# Actualizar chat en memoria con el error | |
st.session_state.chats_in_memory[st.session_state.current_chat_id].update({ | |
'messages': st.session_state.messages, | |
'gemini_history': st.session_state.gemini_history, | |
'title': st.session_state.chat_title | |
}) | |
else: | |
wait_time = (2 ** retry_count) + (time.time() % 1) | |
time.sleep(wait_time) | |
# Salir del bucle si la solicitud fue exitosa | |
break | |