File size: 12,525 Bytes
c76bbac
 
 
 
e75a09b
c76bbac
a339470
956fbae
 
 
 
 
348d961
a316975
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
b1cf5e1
348d961
b1cf5e1
 
 
 
 
 
 
 
 
fa342d1
 
 
 
 
 
 
 
 
 
c0073a3
956fbae
 
9bd45f9
 
5722b78
 
 
 
 
 
 
 
 
 
 
e61bf12
5722b78
 
 
 
 
f6214f2
5722b78
 
 
 
 
 
f6214f2
5722b78
11b06cd
 
 
 
a316975
11b06cd
 
 
 
956fbae
 
 
dc6ebfc
e0af504
 
 
 
dc6ebfc
 
 
 
 
 
 
956fbae
dc6ebfc
 
956fbae
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
ea00e74
956fbae
 
650485d
956fbae
 
 
 
 
 
 
 
97cb8fc
e369da7
956fbae
 
 
e43c82c
11b06cd
e43c82c
 
 
 
 
 
 
 
 
 
 
 
 
956fbae
e369da7
 
956fbae
97cb8fc
a316975
d5a01fe
956fbae
 
 
a316975
956fbae
a316975
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
import time
import os
import joblib
import streamlit as st
import google.generativeai as genai
from dotenv import load_dotenv
from puv_formulas import puv_formulas
from system_prompts import get_puv_system_prompt, get_puv_expert_prompt
from session_state import SessionState

# Inicializar el estado de la sesión
state = SessionState()

# Función para detectar saludos y generar respuestas personalizadas
def is_greeting(text):
    """Detecta si el texto es un saludo simple"""
    text = text.lower().strip()
    greetings = ['hola', 'hey', 'saludos', 'buenos días', 'buenas tardes', 'buenas noches', 'hi', 'hello']
    return any(greeting in text for greeting in greetings) and len(text.split()) < 4

def get_greeting_response():
    """Genera una respuesta amigable para saludos"""
    return """¡Hola! 😊 ¡Qué bueno verte por aquí!

Soy RoboCopy, tu asistente personal para crear Propuestas Únicas de Valor que realmente conecten con tu audiencia.

Mi especialidad es ayudarte a destacar lo que hace único a tu negocio:
• Transformar tus características en beneficios irresistibles
• Crear mensajes que capturan la atención inmediata de tus clientes ideales
• Diferenciarte de la competencia con palabras que venden

¿Te gustaría que continuemos trabajando en tu Propuesta Única de Valor o tienes alguna otra pregunta?"""

# Función para procesar mensajes (unifica la lógica de procesamiento)
def process_message(prompt, is_example=False):
    """Procesa un mensaje del usuario, ya sea directo o de un ejemplo"""
    # Guardar el chat para después si es nuevo
    if state.chat_id not in past_chats.keys():
        # Es una nueva conversación, generemos un título basado en el primer mensaje
        temp_title = f'SesiónChat-{state.chat_id}'
        past_chats[state.chat_id] = temp_title
        
        # Generar título para el chat
        generated_title = state.generate_chat_title(prompt)
        
        # Actualizamos el título en past_chats
        if generated_title:
            state.chat_title = generated_title
            past_chats[state.chat_id] = generated_title
        else:
            state.chat_title = temp_title
    else:
        # Ya existe esta conversación, usamos el título guardado
        state.chat_title = past_chats[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
    state.add_message('user', prompt, USER_AVATAR_ICON)
    
    # Verificar si es un saludo simple
    if is_greeting(prompt):
        # Mostrar respuesta personalizada para saludos
        greeting_response = get_greeting_response()
        with st.chat_message(name=MODEL_ROLE, avatar=AI_AVATAR_ICON):
            st.markdown(greeting_response)
            
        # Añadir respuesta al historial
        state.add_message(
            role=MODEL_ROLE,
            content=greeting_response,
            avatar=AI_AVATAR_ICON,
        )
        state.save_chat_history()
        return
    
    # Procesar respuesta del modelo
    if prompt.strip():
        # Obtener el prompt del experto
        puv_expert_prompt = get_puv_expert_prompt()
        
        # Combinar el prompt del experto con el mensaje del usuario
        enhanced_prompt = f"{puv_expert_prompt}\n\nUser message: {prompt}"
        
        with st.chat_message(MODEL_ROLE, avatar=AI_AVATAR_ICON):
            try:
                if is_example:
                    # Para ejemplos, no necesitamos streaming
                    response = state.chat.send_message(prompt)
                    st.markdown(response.text)
                    
                    # Añadir respuesta al historial
                    state.add_message(MODEL_ROLE, response.text, AI_AVATAR_ICON)
                else:
                    # Para entrada directa, usamos streaming
                    response = state.chat.send_message(
                        enhanced_prompt,
                        stream=True,
                    )
                    
                    message_placeholder = st.empty()
                    full_response = ''
                    
                    # Añadir indicador de "escribiendo..."
                    typing_indicator = st.empty()
                    typing_indicator.markdown("*Generando respuesta...*")
                    
                    # Mostrar respuesta por fragmentos
                    for chunk in response:
                        for ch in chunk.text:
                            full_response += ch
                            time.sleep(0.01)
                            message_placeholder.write(full_response + '▌')
                    
                    # Eliminar indicador de escritura
                    typing_indicator.empty()
                    
                    # Mostrar respuesta completa
                    message_placeholder.write(full_response)
                    
                    # Añadir respuesta al historial
                    state.add_message(
                        role=MODEL_ROLE,
                        content=state.chat.history[-1].parts[0].text,
                        avatar=AI_AVATAR_ICON,
                    )
                
                state.gemini_history = state.chat.history
                
                # Guardar historial actualizado
                state.save_chat_history()
            except ValueError as e:
                st.error("Error: El mensaje no puede estar vacío. Por favor, escribe algo.")

# 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)

# Función de utilidad para mostrar la carátula inicial
def display_initial_header():
    col1, col2, col3 = st.columns([1, 2, 1])
    with col2:
        # Centrar la imagen
        st.markdown("""
            <style>
                div.stImage {
                    text-align: center;
                    display: block;
                    margin-left: auto;
                    margin-right: auto;
                }
            </style>
        """, unsafe_allow_html=True)
        st.image("robocopy_logo.png", width=300, use_container_width=True)
        
        # Título
        st.markdown("""
            <div style='text-align: center; margin-top: -35px; width: 100%;'>
                <h1 class='robocopy-title' style='width: 100%; text-align: center;'>PUV Creator</h1>
            </div>
        """, unsafe_allow_html=True)
        
        # Subtítulo
        st.markdown("""
            <div style='text-align: center; width: 100%;'>
                <p style='font-size: 16px; color: #4ECDC4; width: 100%; text-align: center;'>By Jesús Cabrera</p>
            </div>
        """, unsafe_allow_html=True)
    
    # Descripción (ahora fuera de la columna para ocupar todo el ancho)
    st.markdown("""
        <div style='text-align: center; width: 100%;'>
            <p style='font-size: 16px; background-color: #1E3A5F; padding: 12px; border-radius: 8px; margin-top: 10px; color: #4ECDC4; width: 100%; text-align: center; box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);'>
                🎯 Experto en crear Propuestas de Valor Únicas que convierten audiencia en clientes
            </p>
        </div>
    """, unsafe_allow_html=True)

# Función para mostrar ejemplos de preguntas
def display_examples():
    ejemplos = [
        {"texto": "¿Qué es una Propuesta de Valor Única? 🎯", "prompt": "Explícame qué es una Propuesta de Valor Única (PUV) y por qué es importante para mi negocio"},
        {"texto": "¿Cómo puedo crear mi PUV? 📝", "prompt": "Guíame paso a paso en el proceso de crear una Propuesta de Valor Única efectiva"},
        {"texto": "¿Qué elementos debe tener mi PUV? ✨", "prompt": "¿Cuáles son los elementos esenciales que debe incluir una Propuesta de Valor Única exitosa?"},
        {"texto": "¿Cuál es la mejor fórmula para mi caso? 🤔", "prompt": "Ayúdame a elegir la fórmula más adecuada para mi Propuesta de Valor según mi tipo de negocio"}
    ]

    # Crear los botones de ejemplo
    cols = st.columns(4)
    for idx, ejemplo in enumerate(ejemplos):
        with cols[idx]:
            if st.button(ejemplo["texto"], key=f"ejemplo_{idx}", help=ejemplo["prompt"]):
                state.prompt = ejemplo["prompt"]
                st.rerun()

# Cargar variables de entorno
load_dotenv()
GOOGLE_API_KEY=os.environ.get('GOOGLE_API_KEY')
genai.configure(api_key=GOOGLE_API_KEY)

# Configuración de la aplicación
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

# Crear carpeta de datos si no existe
try:
    os.mkdir('data/')
except:
    # data/ folder already exists
    pass

# Cargar chats anteriores
try:
    past_chats: dict = joblib.load('data/past_chats_list')
except:
    past_chats = {}

# Sidebar para seleccionar chats anteriores
with st.sidebar:
    st.write('# Chats Anteriores')
    if state.chat_id is None:
        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
        state.chat_id = st.selectbox(
            label='Selecciona un chat anterior',
            options=[new_chat_id, state.chat_id] + list(past_chats.keys()),
            index=1,
            format_func=lambda x: past_chats.get(x, 'Nuevo Chat' if x != state.chat_id else state.chat_title),
            placeholder='_',
        )
    # Save new chats after a message has been sent to AI
    state.chat_title = f'SesiónChat-{state.chat_id}'

# Cargar historial del chat
state.load_chat_history()

# Inicializar el modelo
state.initialize_model('gemini-2.0-flash')
state.initialize_chat()

# Mostrar mensajes del historial
for message in state.messages:
    with st.chat_message(
        name=message['role'],
        avatar=message.get('avatar'),
    ):
        st.markdown(message['content'])

# Mensaje inicial del sistema si es un chat nuevo
if not state.has_messages():
    # Mostrar la carátula inicial con el logo centrado
    display_initial_header()
    
    # Mostrar los ejemplos
    display_examples()

    # Inicializar el chat con los prompts del sistema
    system_prompt = get_puv_system_prompt()
    expert_prompt = get_puv_expert_prompt()
    state.chat.send_message(system_prompt)
    state.chat.send_message(expert_prompt)

    # Solo añadir el mensaje inicial si no es un ejemplo
    if not state.has_prompt():
        state.add_message(
            role=MODEL_ROLE,
            content="""👋 Hola, soy RoboCopy.

Tu asistente especializado en crear Propuestas de Valor Únicas (PUVs) que convierten visitantes en clientes.
Puedo ayudarte a:

✅ Crear PUVs impactantes usando diferentes fórmulas
✅ Analizar tu producto/servicio para destacar su valor único
✅ Identificar los elementos clave que atraerán a tu audiencia
✅ Optimizar tu mensaje para diferentes segmentos de mercado

Para empezar a crear PUVs efectivas, necesito conocer:
- ¿Qué producto o servicio ofreces?
- ¿A quién va dirigido? (describe tu público objetivo)

Espero ansioso tu respuesta, para crear tu Propuesta Única de Valor.""",
            avatar=AI_AVATAR_ICON,
        )

# Procesar entrada del usuario
if prompt := st.chat_input('Describe tu producto/servicio y audiencia objetivo...'):
    process_message(prompt, is_example=False)

# Procesar ejemplos seleccionados
if state.has_prompt():
    prompt = state.prompt
    process_message(prompt, is_example=True)
    # Limpiar el prompt
    state.clear_prompt()