File size: 10,955 Bytes
c76bbac
 
 
 
e75a09b
c76bbac
a339470
939772b
956fbae
 
 
 
348d961
a316975
 
 
 
 
20ce570
 
 
 
 
a316975
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
60180b4
a316975
679f2db
 
60180b4
679f2db
 
20ce570
939772b
 
20ce570
 
 
60180b4
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
20ce570
 
 
 
 
 
 
a316975
b1cf5e1
348d961
b1cf5e1
 
 
 
 
 
 
 
 
fa342d1
 
 
 
c0c55b0
fa342d1
c0c55b0
 
fa342d1
 
 
c0073a3
956fbae
 
9bd45f9
 
5722b78
 
 
 
 
 
 
 
 
 
 
e61bf12
5722b78
540dd96
5722b78
 
c0c55b0
f6214f2
5722b78
 
a0d27a3
5722b78
 
c069900
f6214f2
5722b78
11b06cd
c069900
11b06cd
 
c069900
11b06cd
 
 
 
956fbae
 
 
dc6ebfc
e0af504
 
 
 
dc6ebfc
 
 
 
 
 
 
956fbae
dc6ebfc
 
956fbae
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
ea00e74
956fbae
 
650485d
956fbae
 
 
939772b
 
956fbae
97cb8fc
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
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_unified_puv_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']
    
    # Solo considerar como saludo si es el primer mensaje del usuario
    # y es un saludo simple
    is_simple_greeting = any(greeting in text for greeting in greetings) and len(text.split()) < 4
    return is_simple_greeting and len(state.messages) == 0

# 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)
    
    # Preparar el prompt según el tipo de mensaje
    if is_greeting(prompt):
        # Modificamos el prompt para que el modelo tome la iniciativa
        enhanced_prompt = f"El usuario te ha saludado con '{prompt}'. Preséntate brevemente, explica qué es una PUV en 1-2 líneas, y haz 1-2 preguntas iniciales para comenzar a crear la PUV del usuario (como a quién se dirige su producto/servicio o qué ofrece). Sé amigable, breve y toma la iniciativa como el experto que eres."
    elif is_example:
        # Para ejemplos, añadimos instrucciones para respuestas más útiles y conversacionales
        enhanced_prompt = f"El usuario ha seleccionado un ejemplo: '{prompt}'. Responde de manera conversacional y sencilla, como si estuvieras hablando con un amigo. Evita tecnicismos innecesarios. Enfócate en dar información práctica que ayude al usuario a crear su PUV. Usa ejemplos concretos cuando sea posible. Termina tu respuesta con una pregunta que invite al usuario a compartir información sobre su negocio para poder ayudarle a crear su PUV personalizada."
    else:
        # Para mensajes normales, usamos el prompt unificado
        enhanced_prompt = prompt
    
    with st.chat_message(MODEL_ROLE, avatar=AI_AVATAR_ICON):
        try:
            # Usamos streaming para todos los casos
            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: white !important;
        font-weight: bold;
        font-size: clamp(2.5em, 5vw, 4em);
        line-height: 1.2;
    }
    </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 con diseño responsivo (eliminado el símbolo ∞)
        st.markdown("""
            <div style='text-align: center; margin-top: -35px; width: 100%;'>
                <h1 class='robocopy-title' style='width: 100%; text-align: center; color: white !important; font-size: clamp(2.5em, 5vw, 4em); line-height: 1.2;'>PUV Creator</h1>
            </div>
        """, unsafe_allow_html=True)
        
        # Subtítulo con margen superior ajustado a -30px
        st.markdown("""
            <div style='text-align: center; width: 100%;'>
                <p style='font-size: 16px; color: white; width: 100%; text-align: center; margin-top: -20px;'>By Jesús Cabrera</p>
            </div>
        """, unsafe_allow_html=True)
    
    # Descripción con fondo eliminado y margen superior ajustado a -20px
    st.markdown("""
        <div style='text-align: center; width: 100%;'>
            <p style='font-size: 16px; background-color: transparent; padding: 12px; border-radius: 8px; margin-top: -20px; color: white; width: 100%; text-align: center;'>
                🎯 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 el prompt unificado
    system_prompt = get_unified_puv_prompt()
    state.chat.send_message(system_prompt)

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